# 第二次社內賽題解 ## A. 史考特序列 :::spoiler subtask 1 用個set維護 用陣列的話比較麻煩,如果想要快速的把陣列裡重複的刪掉可以用以下 ```cpp= sort( vec.begin(), vec.end() ); vec.erase( unique( vec.begin(), vec.end() ), vec.end() ); ``` ::: ```cpp= #include<bits/stdc++.h> using namespace std; int main(){ ios::sync_with_stdio(0); cin.tie(0); int n, m; cin >> n >> m; int x, y; set<int> a; for(int i = 0; i < n; i++){ cin >> x; a.insert(x); } for(int i = 0; i < m; i++){ cin >> y; if(a.find(y) != a.end()) a.erase(a.find(y)); else a.insert(y); } for(auto i : a) cout << i << ' '; cout << '\n'; } ``` ## B. 幫我撐個10秒左右就好 :::spoiler subtask 1 沒意義,就只是沒有多筆測資 ::: :::spoiler subtask 2 經典括號匹配問題,把左括號存到stack,遇到右括號時看能不能跟stack最上面的配對,可以就pop,否則答案+1 最後答案再加上stack裡未配對到的 ::: ```cpp= #include<bits/stdc++.h> using namespace std; int main(){ ios::sync_with_stdio(0); cin.tie(0); string s; int t; cin >> t; while(t--){ cin >> s; vector<char> stk; int n = s.size(); int cnt = 0; for(int i = 0; i < n; i++){ if(s[i] == '(' || s[i] == '{'){ stk.push_back(s[i]); }else if(s[i] == ')'){ if(stk.empty() || stk.back() != '('){ cnt++; }else stk.pop_back(); }else { if(stk.empty() || stk.back() != '{') cnt++; else stk.pop_back(); } } cout << cnt + stk.size() << '\n'; } } ``` ## C. 修陷運動 :::spoiler subtask 1 看看有誰厲害到做出這個複雜度 ::: :::spoiler subtask 2 每次都減到比前一個少1(除非本來就比較小) ::: ```cpp= #include <bits/stdc++.h> #define pb push_back #define f first #define s second #define rep(X, a,b) for(int X=a;X<b;++X) #define ALL(a) (a).begin(), (a).end() #define SZ(a) (int)(a).size() #define NL "\n" using namespace std; typedef pair<long long,long long> pll; typedef pair<int,int> pii; typedef long long ll; template<typename A, typename B> ostream& operator<<(ostream &os, const pair<A, B> &p) { return os << '(' << p.first << "," << p.second << ')'; } template<typename A> ostream& operator<<(ostream &os, const vector<A> &p){ for(const auto &a:p) os << a << " "; os << "\n"; return os; } int main(){ ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n; cin>>n; vector<int> a(n); rep(i,0,n) cin>>a[i]; ll ans=0; rep(i,1,n){ ans+=max(0, a[i]-a[i-1]+1); a[i]=min(a[i-1]-1, a[i]); } cout<<ans<<NL; } ``` ## D. 字典人2 :::spoiler subtask 1 枚舉所有空格的切割順序,因為每個底線至少配一個字,所以大約最多10個空格,$O(n!)$ 會過 ::: :::spoiler subtask 2 每次切中間就好 $O(n)$ ::: :::spoiler subtask 3 考慮dp,如果要把一個區間都分割完,可以先枚舉第一次切割的位置,再dp把切割點左右邊切完的值,會發現其實解法跟 https://atcoder.jp/contests/dp/tasks/dp_n 一模一樣。 $O(n^3)$ ::: ```cpp= #include <bits/stdc++.h> #define ll long long using namespace std; ll dp[310][310]; int main(){ string str; cin>>str; vector<ll> val; ll cnt=0; for(auto a:str){ if(a=='_') val.push_back(cnt), cnt=0; else cnt++; } val.push_back(cnt); int n=val.size(); for(int k=1;k<n;++k){ for(int i=0;i+k<n;++i){ int j=i+k; ll sum=0; dp[i][j]=1e11; for(int x=i;x<=j;++x) sum+=val[x]; for(int x=i;x<j;++x) dp[i][j]=min(dp[i][j], dp[i][x]+dp[x+1][j]+sum); } } cout<<dp[0][n-1]<<"\n"; } ``` ## E. 資治通鍵 :::spoiler subtask 1 ~~我也不知道怎麼做到這複雜度~~ ::: :::spoiler subtask 2 把題目用圖論的方式思考,把$a_i, b_i$當作邊,發現只有當圖形成鏈時才會yes *鏈即為一條直線* 判斷是鏈與否有兩個關鍵 1. 每個點度一定要<=2 2. 不能出現環(還有可能點度都<=2),這個用個dfs或是dsu(並查集)就可以判斷 $O(n)$ ::: ```cpp= #include<bits/stdc++.h> using namespace std; #define ALAN ios_base::sync_with_stdio(0); cin.tie(0) #define ll long long #define pb push_back #define all(a) (a).begin(), (a).end() #define debug(x) cerr << #x << " = " << x << '\n'; #define rep(X, a,b) for(int X=a;X<b;++X) #define pii pair<int, int> #define pll pair<long long, long long> #define F first #define S second #define lpos pos<<1 #define rpos pos<<1|1 const int MAXN = 1e6+5, MOD = 1e9+7; int ind[MAXN]={0}, n, m, a, b, rec=0; bool vis[MAXN]={0}; vector<int> adj[MAXN]; void dfs(int pa, int v){ if(vis[v]) return; vis[v]=1; for(auto e : adj[v]){ if(e != pa && vis[e]){ rec = 1; return; } else dfs(v, e); } } int main(){ ios_base::sync_with_stdio(false); cin.tie(0); cin >> n >> m; rep(i, 0, m){ cin >> a >> b; ind[a]++, ind[b]++; adj[a].pb(b), adj[b].pb(a); if(ind[a] > 2 || ind[b] > 2){ rec++; break; } } rep(i, 0, n){ if(!vis[i]) dfs(0, i); if(rec) break; } if(rec) cout << "No"; else cout << "Yes"; } ``` *code from justinshao* ## F. 快樂的搖曳露營 :::spoiler subtask 1 $2^n$枚舉選或不選 $O(2^n\times n)$ ::: :::spoiler subtask 2 k=0沒有代價,還b>=0,那就全部都選 ~~甚麼智障subtask~~ $O(n)$ ::: :::spoiler subtask 3 b>0的都選 $O(n)$ ::: :::spoiler subtask 4 做類似longest increasing subsequence的dp dp[i]=選擇的子序列最後一個是第i個露營地的值 轉移:往前看i應該跟哪個露營地接在一起,對於j=1~i-1,dp[i]=max(dp[i], dp[j]+b[i]-issame(i, j)) issame(i, j)是看是不是同種露營地,是的話會return k $O(n^2)$ ::: :::spoiler subtask 5, 6 兩個subtask幾乎一樣 再考慮dp,dp[i]=選擇的子序列最後一個是露營地地區編號為i的最大值 轉移:當迴圈走到i時,有兩種轉移 1. 如果要接在*最後一個為a[i]的子序列*的後面,最大值會是dp[a[i]]+b[i]-k 2. 否則,最大值是max(dp[j]+b[i]),j為任意的露營地編號 算出這兩種再更新答案 $O(n)$時間和空間 但我們又發現過程中只要維護兩個dp值,分別是最大的和次大的。因為如果最大的dp其最後一個露營地編號跟a[i]不一樣,那接再最大的那個必定最佳,因為也不用減k。 那如果最大的dp其最後一個露營地編號=a[i],那次大的一定不會是a[i],所以這時只要看接再最大-k或次大哪個較好 並且不斷更新最大和次大 $O(n)$時間 $O(1)$空間 ::: ```cpp= #include <bits/stdc++.h> #define ll long long using namespace std; int main(){ ios::sync_with_stdio(0); cin.tie(0); ll n, k; cin>>n>>k; vector<pair<ll, ll> > val(n); for(int i=0;i<n;++i) cin>>val[i].first>>val[i].second; ll a=-1, b=-1, va=0, vb=0; for(int i=0;i<n;++i){ ll tmp; if(a!=val[i].first) tmp=val[i].second+va; else{ tmp=max(val[i].second+va-k, val[i].second+vb); } if(tmp>va){ vb=va; b=a; va=tmp; a=val[i].first; } else if(tmp>vb){ vb=tmp; b=val[i].first; } } cout<<va<<"\n"; } ```