The 2024 Damascus University Collegiate Programming Contest === 比賽鏈結 --- https://codeforces.com/gym/105242/ 比賽影片 --- {%youtube JrUnYP879fE%} 記分板 --- ![image](https://hackmd.io/_uploads/B1Tlx1AWyx.png) 題解 --- ### A. Replace with MEX --- #### 題目摘要 給一序列,可以將一個區間的值都改成那區間的mex(此操作最多一次),問整個序列的最大mex #### 想法 :::spoiler 實作 ```cpp= ``` ::: ### B. Powerful String --- #### 題目摘要 設字串的power為$\prod\limits_{i=1}^{m}i^{f_t(t_i)}$,$f_t(t_i)是字元t_i在字串t的出現次數$。給定一個字串,每筆詢問給一個$l,\ r$,問範圍在$[l,\ r]$的子字串能不能交換兩數使power變大 #### 想法 注意到若$i<j,\ f(t_i)>f(t_j)$則power會上升,故對於每個字元,用$f_t$值去排序,並紀錄目前$f_t$值比我小的最右端的位置,看我目前字元的左端有無小於那個右端就好,複雜度$O(n+26q\log n)$ :::spoiler 實作 ```cpp= #include<bits/stdc++.h> using namespace std; #define ll long long #define rep(a, b, c) for (int a = b; a < c; a++) #define pii pair<int, int> #define F first #define S second const int N=2e5+10; vector<int> pos[30]; char s[N]; int pre[N][30]; void solve() { int n; cin >> n; for(int i=1;i<=n;i++) cin >> s[i]; for(int i=0;i<26;i++) pos[i].push_back(0); for(int i=1;i<=n;i++){ for(int j=0;j<26;j++) pre[i][j]=pre[i-1][j]; pre[i][s[i]-'a']++; pos[s[i]-'a'].push_back(i); } for(int i=0;i<26;i++) pos[i].push_back(n+1); int q; cin >> q; while(q--){ int l, r; cin >> l >> r; priority_queue<pii, vector<pii>, greater<pii>> pq; for(int j=0;j<26;j++){ int ocur=pre[r][j]-pre[l-1][j]; if (ocur) pq.push({ocur, j}); } int R=0; int RR=0; int last=0; bool res=0; while(!pq.empty()){ auto [trash, x]=pq.top(); pq.pop(); auto it1=lower_bound(pos[x].begin(), pos[x].end(), l); auto it2=upper_bound(pos[x].begin(), pos[x].end(), r); it2--; if (l<=(*it1)&&(*it1)<=(*it2)&&(*it2)<=r){ if (last!=trash) R=max(R, RR); if (R>(*it1)) res=1; last=trash; RR=max(RR, (*it2)); } } cout << (res?"YES":"NO") << '\n'; } } int main() { ios_base::sync_with_stdio(0), cin.tie(0); solve(); } ``` ::: ### C. Tree Tour --- #### 題目摘要 給一顆樹,問有沒有一個路途經過所有點一或二次 #### 想法 注意到每點度數不能超過3,可以先把這個case判掉,再來標記所有度數3的點,會發現必須要是一條鍊才行,將這些case判掉就好了 :::spoiler 實作 ```cpp= #include<bits/stdc++.h> using namespace std; #define ll long long #define rep(a, b, c) for (int a = b; a < b; a++) const int N=2e5+10; vector<int> v[N]; int in[N]; int rt; void dfs1(int x, int p){ if (in[x]==3) rt=x; for(auto e:v[x]) if (e!=p){ dfs1(e, x); } } bool ans; bool dfs2(int x, int p){ bool res=(in[x]==3); int cnt=0; for(auto e:v[x]) if (e!=p){ bool tmp=dfs2(e, x); cnt+=tmp; res|=tmp; } if (cnt>1) ans=0; return res; } void solve() { int n; cin >> n; for(int i=1;i<=n;i++) in[i]=0; for(int i=1;i<n;i++){ int a, b; cin >> a >> b; v[a].push_back(b); v[b].push_back(a); in[a]++, in[b]++; } int mx=0; for(int i=1;i<=n;i++) mx=max(mx, in[i]); if (mx>=4){ cout << "NO\n"; for(int i=1;i<=n;i++) v[i].clear(); return; } dfs1(1, 0); ans=1; dfs2(rt, 0); cout << (ans?"YES":"NO") << '\n'; for(int i=1;i<=n;i++) v[i].clear(); } int main() { ios_base::sync_with_stdio(0), cin.tie(0); int t; cin >> t; while(t--) solve(); } ``` ::: ### D. You Have Been Grid Squared --- #### 題目摘要 有一個$n\times n$的格子,第$i$行的數從上到下依序是$i,\ i^2,\ i^4,\ i^8...$,問不同數字的個數 #### 想法 經過一番推理可以的到答案是$(非平方數個數\times n+平方數個數)$ :::spoiler 實作 ```cpp= #include<bits/stdc++.h> using namespace std; #define ll long long #define rep(a, b, c) for (int a = b; a < b; a++) const int N=1e5+10; ll dp[N]; map<ll, bool> mp; void solve() { int n; cin >> n; cout << (dp[n]*n+(n-dp[n])) << '\n'; } int main() { ios_base::sync_with_stdio(0), cin.tie(0); dp[1]=0; for(int i=1;i<=100000;i++) mp[1LL*i*i]=1; for(int i=2;i<=100000;i++){ dp[i]=dp[i-1]; if (!mp[i]) dp[i]++; } int t; cin >> t; while(t--) solve(); } ``` ::: ### E. Prefix GCD --- #### 題目摘要 算將一數移除後的最大前綴gcd和 #### 想法 注意到會讓gcd減少的位置最多只會有$\log A_i$個,所以只要把那些點及第一個數抓出來刪掉後計算取max就好,複雜度$O(n\log A_i)$ :::spoiler 實作 ```cpp= #include<bits/stdc++.h> using namespace std; #define ll long long #define rep(a, b, c) for (int a = b; a < c; a++) #define all(x) (x).begin(), (x).end() #define int ll const int IINF = 1e9 + 7; const int N=1e5+10; ll a[N]; void solve() { int n; cin >> n; for(int i=1;i<=n;i++) cin >> a[i]; ll ans=0; int g=a[1]; int g1=a[2]; vector<int> p; for(int i=2;i<=n;i++){ int tmp=__gcd(g, a[i]); g1=__gcd(g1, a[i]); if (tmp!=g) p.push_back(i); g=tmp; ans+=g1; } for(auto x:p){ ll sum=a[1]; g=a[1]; for(int i=2;i<=n;i++) if (i!=x){ g=__gcd(g, a[i]); sum+=g; } ans=max(ans, sum); } cout << ans << '\n'; } main() { ios_base::sync_with_stdio(0), cin.tie(0); int t = 1; // cin >> t; while(t--) solve(); } ``` ::: ### F. Queries on Distincts --- #### 題目摘要 給一個只有小寫字母的字串 Q筆詢問問最小區間$s[i \dots j] \ (l \le i \le j \le r)$裡的distinct char等於 $s[l \dots r]$的distinct char #### 想法 $對每個位置i去記錄從他出發找到j個不同字元的最小右界r$在哪,如此一來我們可以知道要找到j個不同字元從每個位置要延伸的大小,開26個資結(稀疏表、線段樹...)去維護區間最小值。 在回答每筆詢問前,先找到區間不同的字元有幾個,再將qr的範圍做限制,因為有可能找到的答案右界會是超出qr的,然後就直接輸出。 :::spoiler 實作 ```cpp= #include<bits/stdc++.h> using namespace std; #define ll long long #define rep(a, b, c) for (int a = b; a < c; a++) #define all(x) (x).begin(), (x).end() #define pb push_back #define lpos pos << 1 #define rpos pos << 1 | 1 const int IINF = 1e9 + 7; struct SparseTable{ vector<vector<int>> sp; SparseTable(vector<int> &a) { int n = a.size(); sp.resize(n, vector<int>(__lg(n) + 1)); for (int i = n - 1; i >= 0; i--) { sp[i][0] = a[i]; for (int j = 1; i + (1 << j) <= n; j++) { sp[i][j] = min(sp[i][j - 1], sp[i + (1 << j - 1)][j - 1]); } } } auto query(int l, int r) { // [l, r) int k = __lg(r - l); return min(sp[l][k], sp[r - (1 << k)][k]); } }; void solve() { int n; cin >> n; string s; cin >> s; vector<queue<int>> c(26); vector pre(n, vector<int>(26, 0)); rep (i, 0, n) { c[s[i] - 'a'].push(i); if (i) rep (j, 0, 26) pre[i][j] = pre[i - 1][j]; pre[i][s[i] - 'a']++; } set<int> st; rep (i, 0, 26) if (!c[i].empty()) st.insert(c[i].front()); vector to(n, vector<int>(26, n)); rep (i, 0, n) { st.erase(c[s[i] - 'a'].front()); c[s[i] - 'a'].pop(); to[i][0] = i; int id = 0; for (int pos : st) { id++; to[i][id] = pos; } if (!c[s[i] - 'a'].empty()) st.insert(c[s[i] - 'a'].front()); } // rep (i, 0, n) rep (j, 0, 26) { // cout << to[i][j] << " \n" [j == 25]; // } vector<SparseTable> distinct; // 0-base rep (i, 0, 26) { vector<int> tmp; rep (j, 0, n) { if (to[j][i] == n) tmp.pb(n); else tmp.pb(to[j][i] - j + 1); } SparseTable sp(tmp); distinct.pb(sp); } int q; cin >> q; while (q--) { int l, r; cin >> l >> r; l--, r--; int d = -1; rep (i, 0, 26) { d += ((pre[r][i] - (l ? pre[l - 1][i] : 0)) > 0); } int L = l, R = r + 1; while (R - L > 1) { int mid = L + R >> 1; if (to[mid][d] <= r) L = mid; else R = mid; } r = L + 1; // cout << d << ' ' << l << ' ' << r << '\n'; cout << distinct[d].query(l, r) << '\n'; } } int main() { ios_base::sync_with_stdio(0), cin.tie(0); int t = 1; // cin >> t; while(t--) solve(); } ``` ::: ### G. Lexicographically Maximum --- #### 題目摘要 #### 想法 判case+greedy :::spoiler 實作 ```cpp= #include<bits/stdc++.h> using namespace std; #define ll long long #define rep(a, b, c) for (int a = b; a < c; a++) #define all(x) (x).begin(), (x).end() #define pb push_back #define lpos pos << 1 #define rpos pos << 1 | 1 const int IINF = 1e9 + 7, MAXN = 1e6 + 5; void solve() { string s; cin >> s; int n = s.size(); vector<queue<int>> q(26); rep (i, 0, n) { q[s[i] - 'a'].push(i); } set<int> st; rep (i, 0, 26) if (!q[i].empty()) st.insert(q[i].front()); string t = ""; rep (i, 0, n) { if (st.size() > s[i] - 'a' + 1) { int id = *st.rbegin(); id++; while (id < n && s[id] - 'a' + 1 <= st.size()) id++; int cur = i; int sz = st.size(); while (cur < id) { t.pb('a' + sz - 1); st.erase(q[s[cur] - 'a'].front()); q[s[cur] - 'a'].pop(); if (!q[s[cur] - 'a'].empty()) st.insert(q[s[cur] - 'a'].front()); cur++; } i = id - 1; } else { auto it = st.begin(); if (st.size() == s[i] - 'a' + 1 && next(it) != st.end() && s[*next(it)] < s[i]) { int id = *st.rbegin(); id++; while (id < n && s[id] - 'a' + 1 <= st.size()) id++; int cur = i; int sz = st.size(); while (cur < id) { t.pb('a' + sz - 1); st.erase(q[s[cur] - 'a'].front()); q[s[cur] - 'a'].pop(); if (!q[s[cur] - 'a'].empty()) st.insert(q[s[cur] - 'a'].front()); cur++; } i = id - 1; } else { t.pb(s[i]); st.erase(q[s[i] - 'a'].front()); q[s[i] - 'a'].pop(); if (!q[s[i] - 'a'].empty()) st.insert(q[s[i] - 'a'].front()); } } } cout << t << '\n'; } int main() { ios_base::sync_with_stdio(0), cin.tie(0); int t = 1; // cin >> t; while(t--) solve(); } ``` ::: ### H. Banis Hotel --- #### 題目摘要 #### 想法 :::spoiler 實作 ```cpp= ``` ::: ### I. Minimum XOR --- #### 題目摘要 #### 想法 :::spoiler 實作 ```cpp= ``` ::: ### J. The Square Game --- #### 題目摘要 Ahmad和Ahmad玩遊戲,問誰贏 #### 想法 Ahmad :::spoiler 實作 ```cpp= #include<bits/stdc++.h> using namespace std; #define ll long long #define rep(a, b, c) for (int a = b; a < b; a++) void solve() { cout << "Ahmad\n"; } int main() { solve(); } ``` ::: ### K. 2.. 3.. 4.. Colorful! Colorful! Colorful! --- #### 題目摘要 #### 想法 :::spoiler 實作 ```cpp= ``` ::: ### L. Median of the Array --- #### 題目摘要 定義中位數是$a$排序完後的$a_{\lfloor \frac{n+1}{2}\rfloor}$,給定數列$a$問能不能拆出兩個數列$b,c$使得$b,c$數列的中位數相等 #### 想法 不難發現,如果$b,c$中位數相等,那一定也是$a$的中位數。 所以只要想好怎麼分就好,如果$n$是奇數,那必定會拆出一個奇數一個偶數大小的數列,奇數大小的就選中位數就好,那麼剩下兩種情況,原數列中間部分是$\{a,b,b\}$或是$\{a,a,b\}$,左邊的case必定是爛的。 如果$n$是偶數,中間部分會是$\{a,b,b,c\}$或$\{a,a,b,c\}$,兩個都能拆出答案,所以只要檢查是不是其中一個就好。 :::spoiler 實作 ```cpp= #include<bits/stdc++.h> using namespace std; #define ll long long #define rep(a, b, c) for (int a = b; a < c; a++) const int N=2e5+10; ll a[N]; void solve() { int n; cin >> n; rep(i,0,n) cin >> a[i]; // rep(i,0,n) cout << a[i]; sort(a,a+n); if(n==2) {(a[0]==a[1]) ? cout <<"YES\n":cout<<"NO\n";} else{ int idx = (n-1)/2; if(n&1){ a[idx]==a[idx-1] ? cout <<"YES\n":cout<<"NO\n";; }else{ (a[idx]==a[idx+1] || a[idx]==a[idx-1]) ? cout <<"YES\n":cout<<"NO\n";; } } } int main() { ios_base::sync_with_stdio(0), cin.tie(0); int t; cin >> t; while(t--) solve(); } ``` ::: ### M Taim and Zingers. --- #### 題目摘要 給$n$,你可以偷走最多$k$個東西使$n=n-k$,接著如果$n$是三的倍數,那你會拿到$\frac{n}{3}$,否則如果$n$是二的倍數,那你會拿到$\frac{n}{2}$,否則你會拿到$n$,問你能拿到的$n+k$ 的最大值。 #### 想法 發現做$7$次後對$2,3$的餘數關係就會有一次循環,所以只要做$\min(k,7)$次,對答案取$\max$就好 :::spoiler 實作 ```cpp= #include<bits/stdc++.h> using namespace std; #define ll long long #define rep(a, b, c) for (int a = b; a < b; a++) ll s(ll a){ if(a%3==0) return a/3; else if(a%2==0) return a/2; else return a; } void solve() { ll n,k; cin >> n >> k; ll ans = 0, sum=s(n); k = min(k,7LL); k = min(k,n); for(int i = 1; i <= k ;i++){ sum = max(sum,s(n-i)+i); } cout << sum << '\n'; } int main() { ios_base::sync_with_stdio(0), cin.tie(0); int t; cin >> t; while(t--)solve(); } ``` :::