--- tags: 成大高階競技程式設計 2021 image: https://i.imgur.com/IIj9BcL.png --- # 2021 高階競程 Contest #1 - 題解 ## [鮪魚吃蛋糕](https://judge.csie.ncku.edu.tw/problems/89) - Task Provider: leo - Task Setter: leo ### 首殺 team21_24 (00:13) ### 解法說明 你可以把所有蛋糕依照好吃度 $d$ 與價錢 $c$ 的比值,由大到小排序。 接著依序拿蛋糕直到錢不夠為止。 時間複雜度為排序的 $O(N\log N)$。 實作上需要特別注意一些問題: - 雖然這題因為是浮點數除法,基本上不太會遇到這問題, 不過在寫根據比值排序的 compare 時, 儘量不要用除的,因為分母有可能為 $0$,下方提供範例。 浮點數則因為除以 $0$ 會變成 `inf` 所以不太會出問題, 不過還是可能被一些浮點數精度的問題影響到。 :::spoiler 範例 ```cpp struct cake{ int d, c; }; // Suggest bool compare1(const cake &a, const cake &b){ return (long long)a.d * b.c > (long long)b.d * a.c; } // Not suggest bool compare2(const cake &a, const cake &b){ return (double)a.d / a.c > (double)b.d / b.c; } ``` ::: <br> - 因為題目採用相對誤差的比對方式, 所以其實可以儘量輸出很多位數,以求較精確的輸出, 請參考下方標準程式 - 如果 $K$ 為 $0$ 時不能直接跳出去,因為蛋糕可能也是 $0$ 元 如果題目改成蛋糕不可分割的話,這種依照 CP 值的 greedy 方法是不行的, 詳細原因與解法會在第七週的課程教到,如果有興趣的話可以先自己想想看為什麼。 ### 標準程式 :::spoiler ```cpp #include <iostream> #include <iomanip> #include <algorithm> using namespace std; struct cake{ int d, c; bool operator<(const cake &a)const{ return (long long)d * a.c > (long long)a.d * c; } } a[200000]; int main() { ios::sync_with_stdio(0); cin.tie(0); long long n, k; double ans = 0; cin >> n >> k; for(int i = 0; i < n; i++) cin >> a[i].d >> a[i].c; sort(a, a + n); for(int i = 0; i < n && (k > 0 || a[i].c == 0); k -= a[i].c, i++) if(k >= a[i].c || a[i].c == 0) ans += a[i].d; else ans += a[i].d * (double(k) / a[i].c); cout << fixed << setprecision(12) << ans << "\n"; } ``` ::: ## [零二](https://judge.csie.ncku.edu.tw/problems/90) - Task Provider: arashi87 - Task Setter: arashi87 ### 首殺 team21_34 (00:38) ### 解法說明 題目要求你將 $N$ 分解為 $K$ 個二的幂次,其實就是單純的二進制分解而已,我們可以很容易的知道下面兩種情況會無法分解。 - 第一種是 $K \gt N$,當發生這種情況即使將 $N$ 通通分解成 $2^0$ 也會不夠 - 第二種是當 $N$ 轉換的二進制裡 `1` 的個數大於 $K$ 時也會無法分解,因為能夠分解的最少數字,其數量已經大於 $K$ 了。 而剩下就是如何分解而已,這邊我們利用 priority queue 可以自動找出最大值的功能,先將 $N$ 做二進制分解並將分解出來的數字都放進 `pq` 裡,如果數量小於 $K$ 的話就從 `pq` 取出最大的數除以 $2$,生成兩個數字,並且放回 `pq`,這樣做一次可以使得 $pq.size$ 增加 $1$,如此只需要一直重複直到 $pq.size = K$。時間複雜度為 priority queue 單次操作的 $O(\log K)$ 乘上總操作次數 $O(K)$,複雜度為 $O(K\log K)$。 總複雜度為分解 $N$ 的 $O(\log N)$ 加上後面 priority queue 的 $O(K\log K)$,因此為 $O(\log N+K\log K)$。 下方的解法 2 是複雜度 $O(K+\log N)$ 的做法,比起上面的解法又更快了一點,有興趣的可以自己研究看看。 ### 標準程式 :::spoiler 解法 1 ```cpp #include <queue> #include <stdio.h> using namespace std; int n, k, base = 1; priority_queue<int> pq; int main() { scanf("%d%d", &n, &k); if (k > n) return puts("NO"), 0; while (n) { if (n & 1) pq.push(base); n >>= 1, base <<= 1; } if (pq.size() > k) return puts("NO"), 0; while (pq.size() < k) { int tmp = pq.top(); pq.pop(); pq.push(tmp >> 1), pq.push(tmp >> 1); } puts("YES"); while(!pq.empty()) printf("%d ", pq.top()), pq.pop(); printf("\n"); } ``` ::: :::spoiler 解法 2 ```cpp #include <iostream> using namespace std; int main() { ios::sync_with_stdio(0); cin.tie(0); long long n, k, a[64] = {}, cnt = 0; bool flag = 0; cin >> n >> k; for(long long i = 0, tmp = 1; i < 64; tmp <<= 1, i++) if(tmp & n) a[i] = 1, cnt++; if(cnt > k || n < k) return cout << "NO\n", 0; for(int i = 63; i > 0; i--) if(cnt < k){ int tmp = min(k - cnt, a[i]); a[i] -= tmp; a[i - 1] += tmp * 2; cnt += tmp; } cout << "YES\n"; for(long long i = 0, tmp = 1; i < 64; tmp <<= 1, i++) for(int j = 0; j < a[i]; j++){ if(flag) cout << " "; else flag = 1; cout << tmp; } cout << "\n"; } ``` ::: ## [BNT](https://judge.csie.ncku.edu.tw/problems/91) - Task Provider: D4nnyLee - Task Setter: D4nnyLee ### 首殺 team21_12 (00:02) ### 解法說明 #### Subtask 1 $O(N^3)$ 因為這個子任務 $N$ 最大只有到 $100$,因此我們可以直接枚舉所有可能的長度為 $3$ 的子序列,並算出當中有多少是 BNT。 :::spoiler 參考程式 ```cpp #include <iostream> using namespace std; int main(void) { ios_base::sync_with_stdio(false); cin.tie(nullptr); int N; string s; cin >> N >> s; int ans{0}; for (int i{0}; i < N; ++i) for (int j{i + 1}; j < N; ++j) for (int k{j + 1}; k < N; ++k) if (s[i] == 'B' && s[j] == 'N' && s[k] == 'T') ++ans; cout << ans << '\n'; return 0; } ``` ::: #### Subtask 2 $O(N)$ #### 解法 1 我們枚舉所有的 N,對於每個找到的 N,若左邊有 $x$ 個 B 以及 $y$ 個 T,從左邊任意取一個 B 再從右邊任意取一個 T 就可以組成一個 BNT 子序列,因此對於這個 N,我們可以得到 $x \cdot y$ 個 BNT 子序列。 #### 解法 2 我們定義一個長度為 $3$ 的陣列 $cnt$,每個元素代表的意義如下: * $cnt_0$ 代表目前遇到的 B 的個數 * $cnt_1$ 代表目前遇到的 BN 的個數 * $cnt_2$ 代表目前遇到的 BNT 的個數 因此我們只要把 $s$ 從左到右跑一次,並且維護 $cnt$ 的元素,最後的 $cnt_2$ 就是我們要的答案。 > 維護方法: > * 每個 N 都可以接在目前遇到的 B 的後面,形成 $cnt_0$ 個 BN > * 每個 T 都可以接在目前遇到的 BN 的後面,形成 $cnt_1$ 個 BNT 要注意答案可能會超出 `int` 的範圍,因此型別需要設為 `long long`。 ### 標準程式 :::spoiler 解法 1 ```cpp #include <iostream> using namespace std; int main(void) { ios_base::sync_with_stdio(false); cin.tie(nullptr); int N; string s; cin >> N >> s; long long x{0}, y{0}, ans{0}; for (int i{0}; i < N; ++i) if (s[i] == 'T') ++y; for (int i{0}; i < N; ++i) { if (s[i] == 'B') ++x; else if (s[i] == 'T') --y; else ans += x * y; // s[i] == 'N' } cout << ans << '\n'; return 0; } ``` ::: :::spoiler 解法 2 ```cpp #include <iostream> #include <array> using namespace std; int main(void) { ios_base::sync_with_stdio(false); cin.tie(nullptr); int N; string s; cin >> N >> s; array<long long, 3> cnt{}; for (int i{0}; i < N; ++i) { if (s[i] == 'B') ++cnt[0]; else if (s[i] == 'N') cnt[1] += cnt[0]; else // s[i] == 'T' cnt[2] += cnt[1]; } cout << cnt[2] << '\n'; return 0; } ``` ::: ## [遞增數列](https://judge.csie.ncku.edu.tw/problems/92) - Task Provider: ys - Task Setter: ys ### 首殺 team21_18 (00:26) ### 解法說明 #### 解法 1 設 $p(n, m)$ 表示上限為 $n$ 且長度為 $m$ 的遞增數列個數 首先觀察到,長度 $m$ 可由長度 $m-1$ 的遞增數列推得,其關係式為: $$ p(n, m) = p(n, m-1) + p(n-1, m-1) + \cdots + p(1, m-1) $$ 例如 $n = 2, m = 3$ 的遞增數列 $a$ 可分為 - $a_3 = 2$: $\textbf{2, 2}, 2$ $\textbf{1, 2}, 2$ $\textbf{1, 1}, 2$ - $a_3 = 1$: $\textbf{1, 1}, 1$ 觀察得知 $p(2, 3)$ 有以下關係式 $$ p(2, 3) = p(2, 2) + p(1, 2) $$ > 將尾端 $a_3$ 設為 $2$ 時,$a_1, a_2$ 的可能數為 $p(2, 2)$; 將尾端 $a_3$ 設為 $1$ 時,$a_1, a_2$ 的可能數為 $p(1, 2)$ 所以欲求 $p(n, m)$, 只需要依序將 $p(1, 1), p(2, 1), \cdots , p(n, 1)$ 求出來, 再將 $p(1, 2), p(2, 2), \cdots , p(n, 2)$ 求出來, 依此類推,就能得到題目所需的目標答案 實作上由於結果相當龐大,做加法時會除餘要求的 $10^9 + 7$ 如 `a = (b + c) % 1000000007` 並且設邊界 $\forall n. p(n, 0) = 1$,因為 $m = 0$ 的時候只有**一種**可能 #### 解法 2 $n-1+m$ 個格子**任選** $m$ 個格子,令**未選到的格子**為隔板 選到的格子填上數字,規則如下: - 不超過第 $1$ 個隔板之前為 $1$ - 第 $k-1$ 個到第 $k$ 個隔板之間為 $k$ - 超過第 $n-1$ 個隔板之後為 $n$ 例如 $n = 2, m = 3$ 的格子為 `_,_,_,_`,有以下可能選法: - `|,_,_,_` - `_,|,_,_` - `_,_,|,_` - `_,_,_,|` 其中 `|` 表示隔板 (未選到的格子) 根據規則將選到的格子填上數字: - `|,2,2,2` - `1,|,2,2` - `1,1,|,2` - `1,1,1,|` 格子選法數剛好為遞增數列的可能數,可用排列組合的計算方式算出 ### 標準程式 :::spoiler 解法 1 ```cpp #include<bits/stdc++.h> using namespace std; int const maxn = 1e3 + 10; int const mod = 1000000007; int n, m, p[maxn][25]; int main() { cin >> n >> m; for(int i = 1; i <= n; i++) p[i][0] = 1; for(int r = 0; r < m; r++) // r := round for(int b = n; b >= 1; b--) { // b := bound long long sum = 0; for(int i = 1; i <= b; i++) sum = (sum + p[i][r]) % mod; p[b][r+1] = sum; } cout << p[n][m] << endl; return 0; } ``` ::: :::spoiler 解法 2 ```python from math import factorial as fact mod = 10**9 + 7 def C(n, k): return fact(n) // (fact(k) * fact(n - k)) n, m = map(int, input().split()) print(C(n + m - 1, m) % mod) ``` ::: ## [な NA](https://judge.csie.ncku.edu.tw/problems/93) - Task Provider: raiso - Task Setter: raiso ### 首殺 team21_15 (00:13) ### 解法說明 這題有兩個要點需要注意: - $\text{len}(M)$ 至少為 $1$ - 只需要考慮 $\text{len}(M) = 1$ or $2$ 的情況,因為對於長度更長的 $M$,我們只需要將其同時去頭去尾後,所得到的吸引力只會大於等於還沒去頭去尾的情況。 **超越** 本題可以使用 $O(N\log N)$ 的複雜度實作 - 將比對字串的過程看成多項式相乘,枚舉所有 $M$ 得到的吸引力其實就是多項式相乘後的結果,由於多項式相乘可以使用 FFT 與 IFFT 的方式加速,在 $O(N\log N)$ 的複雜度得到答案,由於整個過程過於神奇,有興趣的人可以自行研究。 ### 標準程式 :::spoiler 解法 O(N^2) ```cpp #include <bits/stdc++.h> #define pb push_back #define mp make_pair #define Int long long #define x first #define y second using namespace std; void solve(){ int n; cin >> n; string str; cin >> str; vector<int> A(n); for(int i = 0; i < n; i++) { if(str[i] == 'A') A[i] = 2; if(str[i] == 'T') A[i] = 1; if(str[i] == 'C') A[i] = -2; if(str[i] == 'G') A[i] = -1; } int ans = 0; for(int c = 1; c <= 2; c++) { for(int i = 1; i < n-c; i++) { int cnt = 0, j = 1; while((i+c-1+j) < n && (i-j) >= 0) { int tmp = A[i+c-1+j] + A[i-j]; if(tmp == 3 || tmp == -3) cnt++; j++; } ans = max(ans, cnt); } } cout << ans << endl; } int main(){ ios::sync_with_stdio(0); cin.tie(0); int t=1; //cin >> t; while(t--) solve(); return 0; } ``` ::: :::spoiler 解法 O(NlogN) ```cpp= #include <bits/stdc++.h> #define pb push_back #define mp make_pair #define add insert #define Int long long #define pdd pair<double,double> #define pii pair<int,int> #define pII pair<Int,Int> #define sqr(x) ((x)*(x)) #define PI acosl(-1) #define MEM(x) memset(x,0,sizeof(x)) #define x first #define y second using namespace std; typedef complex<double> CD; void FFT(vector<CD> &a, bool inv) { //bit reversal int n = a.size(); for(int i=0, j=0; i < n; i++) { if(j > i) swap(a[i], a[j]); int k = n; while(j & (k >>=1)) j &=~k; j |= k; } double pi = inv? -PI:PI; for(int step=1; step < n; step <<= 1) { double alpha = pi/step; for(int k = 0; k < step; k++) { CD omegak = exp(CD(0, alpha*k)); for(int Ek = k; Ek < n; Ek += step << 1) { int Ok = Ek + step; CD t = omegak * a[Ok]; a[Ok] = a[Ek] - t; a[Ek] += t; } } } if(inv) for(int i = 0; i < n; i++) a[i] /= n; } inline vector<double> operator * (const vector<double>& v1, const vector<double>& v2) { int s1 = v1.size(), s2 = v2.size(), S = 2; while(S < s1+s2) S <<= 1; vector<CD> a(S,0), b(S,0); for(int i = 0; i < s1; i++) a[i] = v1[i]; for(int i = 0; i < s2; i++) b[i] = v2[i]; FFT(a, false); FFT(b, false); for(int i = 0; i < S; i++) a[i] *= b[i]; FFT(a, true); vector<double> res(s1+s2-1); for(int i = 0; i < s1+s2-1; i++) res[i] = a[i].real(); return res; } bool match(char a, char b) { if(a > b) swap(a, b); if(a == 'A' && b == 'T') return 1; else if(a == 'C' && b == 'G') return 1; return 0; } void solve(){ int n; cin >> n; string str; cin >> str; vector<double> A(n,0), B(n,0), res1, res2; vector<int> res; for(int i = 0; i < n; i++) if(str[i] == 'A') A[i] = 1.0; for(int i = 0; i < n; i++) if(str[i] == 'T') B[i] = 1.0; res1 = A*B; A.clear(); B.clear(); A.assign(n, 0); B.assign(n, 0); for(int i = 0; i < n; i++) if(str[i] == 'C') A[i] = 1.0; for(int i = 0; i < n; i++) if(str[i] == 'G') B[i] = 1.0; res2 = A*B; res.resize(res1.size()); int nn = res.size(); for(int i = 0; i < nn; i++) res[i] = int(res1[i] + res2[i]); for(int i = 0; i < n; i++) if(match(str[i], str[i+1])) res[i*2+1]--; int maxi = 0; for(int i = 0; i < nn; i++) maxi = max(maxi, res[i]); cout << maxi << endl; } int main(){ ios::sync_with_stdio(0); cin.tie(0); int t=1; //cin >> t; while(t--) solve(); return 0; } ``` :::