# 模擬競賽 I 題解 ## 2021 師大附中暑期資訊培訓 joylintp / WiwiHo --- <!-- .slide: data-background="https://i.imgur.com/AJyPIqE.jpg" --> <font color="black"> <font size=7><b> 直播循環 (Cycle) </b></font> <font size=6><b> 來源: [Codeforces 1041C](https://codeforces.com/problemset/problem/1041/C) </b></font> <font size=6><b> 測資: joylintp </b></font> <font size=6><b> 題敘: joylintp </b></font> </font> ---- ### 直播循環 - Subtask 1 $r=0$ → 不用休息 → 所有遊戲都可以在第一個循環直播 → $k=1,x_i=1$ <font color="yellow">3 / 100</font> ---- ### 直播循環 - Subtask 3 $n \le 10$ → 枚舉直播遊戲的順序 → 盡量讓遊戲可以在同一個循環內直播 → $O(n! \times n)$ <font color="yellow">9 / 100</font> ---- ### 直播循環 - Subtask 2 $n = s$ → 每天都有一種遊戲要玩 → 第一個循環玩第 $1,1+r+1,1+2r+2,\dots$ 天的遊戲 → 第二個循環玩第 $2,2+r+1,2+2r+2,\dots$ 天的遊戲 → $O(n)$ <font color="yellow">13 / 100</font> ---- ### 直播循環 - Subtask 2 #### 貪心法 ---- ### 直播循環 - Subtask 4 每次盡量選擇還沒玩過的遊戲中 $a_i$ 最小的 → $O(n)$ 迴圈尋找可玩的遊戲 → 總複雜度 $O(n^2)$ <font color="yellow">18 / 100</font> ---- ### 直播循環 - Subtask 5 ~~→ $O(n)$ 迴圈尋找可玩的遊戲~~ → `set`、`priority_queue` $O(\log{n})$ 維護 → 總複雜度 $O(n\log{n})$ <font color="lightgreen">100 / 100</font> ---- ```cpp= #include<bits/stdc++.h> using namespace std; #define MP make_pair #define F first #define S second signed main() { ios_base::sync_with_stdio(false); cin.tie(0); int n, m, d; cin >> n >> m >> d; vector<pair<int, int>> a(n); for (int i = 0; i < n; i++) cin >> a[i].F, a[i].S = i; sort(a.begin(), a.end()); vector<int> ans(n); priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq; for (int i = 0; i < n; i++) if (pq.empty() || a[i].F - pq.top().F < d + 1) ans[a[i].S] = pq.size() + 1, pq.push(MP(a[i].F, pq.size() + 1)); else ans[a[i].S] = pq.top().S, pq.pop(), pq.push(MP(a[i].F, ans[a[i].S])); cout << pq.size() << '\n'; for (int i : ans) cout << i << ' '; return 0; } ``` --- <!-- .slide: data-background="https://imgur.com/F3Zflqt.jpg" --> <font color="black"> <font size=7><b> 兔田彩券 (Lottery) </b></font> <font size=6><b> 靈感來源: [JOI 2021 Final Round pC](https://oj.uz/problem/view/JOI21_ho_t3) </b></font> <font size=6><b> 測資: WiwiHo </b></font> <font size=6><b> 題敘: joylintp </b></font> </font> ---- ## 題目大意 有 $m$ 個 pair $(a_i,b_i)$ 對於每筆詢問 $(l_i,r_i,s_i,t_i)$ 求有幾個 $j$ 滿足 $l_i \leq a_j \leq r_i \land s_i \leq b_j \leq t_i$ 或 $l_i \leq b_j \leq r_i \land s_i \leq a_j \leq t_i$ ---- ## Subtask 1 $n,m,q \leq 500$ 對於每一個詢問 把所有 pair 檢查一次是否符合條件就好了 $O(mq)$ ---- ### 常見錯誤 1. 同一個算到兩次 2. 把「或」當成「且」 ---- ## Subtask 2 $n \leq 100$,$q \leq 10000$ $n$ 很小? pair 只有 $O(n^2)$ 種而已 數每一種 pair 出現了幾次 詢問的時候,找符合條件的 pair $O(m + n^2q)$ ---- ## Subtask 3 $l_i \leq r_i < s_i \leq t_i$ 如果把每一種 pair 出現的數量存在一個表格上 那詢問其實是在問一個矩形區域的和 假設 $(i,j)$ 出現 $c[i][j]$ 次 二維前綴和:$sum[i][j]= \sum_{i'=1}^i \sum_{j'=1}^j c[i'][j']$ ---- $\sum_{i=l}^{r} \sum_{j=s}^t c[i][j] = sum[r][t] \\ - sum[l-1][t] - sum[r][s-1] + sum[l-1][s-1]$ 預處理 $sum$,詢問只要 $O(1)$ 的時間 $O(m + n^2 + q)$ ---- ## Subtask 4 為什麼上一個 subtask 的 code 不能過這個? 因為會算到重複的 如果 $l_i \leq a_j \leq r_i \land s_i \leq b_j \leq t_i$ 和 $l_i \leq b_j \leq r_i \land s_i \leq a_j \leq t_i$ 同時滿足,$j$ 就會被算到兩次 ---- 誰會被算到兩次? $\max(l_i,s_i) \leq a_j,b_j \leq \min(r_i,t_i)$ $j$ 就會被算到兩次 把這個區域扣掉就好 ---- ```cpp= #include <bits/stdc++.h> #define StarBurstStream ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0); using namespace std; int main(){ StarBurstStream int n, m; cin >> n >> m; vector<vector<int>> a(n + 1, vector<int>(n + 1)); for(int i = 0; i < m; i++){ int u, v; cin >> u >> v; if(u > v) swap(u, v); a[u][v]++; a[v][u]++; } for(int i = 1; i <= n; i++){ for(int j = 1; j <= n; j++) a[i][j] += a[i][j - 1]; } for(int i = 1; i <= n; i++){ for(int j = 1; j <= n; j++) a[j][i] += a[j - 1][i]; } auto calc = [a](int x1, int y1, int x2, int y2){ return a[x2][y2] - a[x2][y1 - 1] - a[x1 - 1][y2] + a[x1 - 1][y1 - 1]; }; int q; cin >> q; while(q--){ int l, r, s, t; cin >> l >> r >> s >> t; int tl = max(l, s); int tr = min(r, t); int ans = calc(l, s, r, t); if(tl <= tr) ans -= calc(tl, tl, tr, tr) / 2; cout << ans << "\n"; } return 0; } ``` --- <!-- .slide: data-background="https://imgur.com/Bk0PYSd.jpg" --> <font color="black"> <font size=7><b> 魔法師測驗 (Mage) </b></font> <font size=6><b> 來源: [CF 1101D](https://codeforces.com/problemset/problem/1101/D) </b></font> <font size=6><b> 測資: joylintp </b></font> <font size=6><b> 題敘: joylintp </b></font> </font> ---- ### 魔法師測驗 - Subtask 1 $m_1=m_2=\ldots=m_n$ → 若 $m_1=1$ 則答案為 $0$(和諧度必為 $1$) → 若 $m_1 \ne 1$ 則答案為樹直徑(和諧度必不為 $1$) <font color="yellow">8 / 100</font> ---- ### 魔法師測驗 - Subtask 2, 6 $n \le 1000$ → 枚舉起點 → $O(n)$ DFS 看能走多遠 → 總複雜度 $O(n^2)$ <font color="yellow">(6 + 14) / 100</font> ---- ### 魔法師測驗 和諧度不為 $1$ → 最大公因數大於 $1$ → 所有數字至少有一個共同質因數 ---- ### 魔法師測驗 - Subtask 5 $m_i \le 100$ → 枚舉質數(至多 $25$ 個) → 對每個質數建出子圖 → 找所有子圖中最長的樹直徑 → 總複雜度 $O(n \times 25)$ <font color="yellow">16 / 100</font> ---- ### 魔法師測驗 - Subtask 7 $m_i \le 2 \times 10^5$ → 質數有 $17984$ 個 → 對每個質數建出子圖 → TLE ---- ### 魔法師測驗 - Subtask 7 $m_i \le 2 \times 10^5$ → $2 \times 3 \times 5 \times 7 \times 11 \times 17 > 2 \times 10^5$ → 每個節點的相異質因數不會超過 $5$ 個 → 樹 DP ---- ### 魔法師測驗 - Subtask 7 #### 樹 DP 記錄 * 每個節點回傳各質因數可往下延伸多長 轉移 * 繼續延伸子節點質因數的長度 * 將兩個子節點質因數的長度接起來 總複雜度 $O(n\log^2{m})$ <font color="lightgreen">100 / 100</font> ---- ```cpp= #include<bits/stdc++.h> using namespace std; #define F first #define S second int ans; vector<bool> pfac(200001, true), vis; vector<int> prime, a; vector<vector<int>> edge; map<int, int> dfs(int u) { vis[u] = true; map<int, int> sa; for (int i : prime) { if (i * i > a[u]) break; while (a[u] % i == 0) sa[i] = 1, a[u] /= i; } if (a[u] != 1) sa[a[u]] = 1; map<int, pair<int, int>> sb; for (int i : edge[u]) if (!vis[i]) { map<int, int> mp = dfs(i); for (auto p : mp) if (sa.count(p.F)) { sa[p.F] = max(sa[p.F], p.S + 1); if (p.S > sb[p.F].F) sb[p.F].S = sb[p.F].F, sb[p.F].F = p.S; else if (p.S > sb[p.F].S) sb[p.F].S = p.S; else; } } for (auto p : sa) ans = max(ans, p.S); for (auto p : sb) ans = max(ans, p.S.F + p.S.S + 1); return sa; } signed main() { ios_base::sync_with_stdio(false); cin.tie(0); for (int i = 2; i <= 200000; i++) { if (pfac[i]) prime.push_back(i); for (int j : prime) { if (i * j > 200000) break; pfac[i * j] = false; if (i % j == 0) break; } } int n; cin >> n, vis.resize(n + 1), a.resize(n + 1), edge.resize(n + 1); for (int i = 1; i <= n; i++) cin >> a[i]; int u, v; for (int i = 1; i < n; i++) cin >> u >> v, edge[u].push_back(v), edge[v].push_back(u); dfs(1); cout << ans << '\n'; return 0; } ``` --- <!-- .slide: data-background="https://imgur.com/njdirBI.jpg" --> <font color="black"> <font size=7><b> 刷怪塔 (Monster) </b></font> <font size=6><b> 來源: [Google Code Jam 2020 Round 2 pA](https://codingcompetitions.withgoogle.com/codejam/round/000000000019ffb9/00000000003384ea) </b></font> <font size=6><b> 測資: joylintp </b></font> <font size=6><b> 題敘: joylintp </b></font> </font> ---- ### 刷怪塔 - Subtask 3 $a,b \le 1000$ → 選擇刷怪塔的次數至多 $\sqrt{a+b}$ 次 → 模擬每次選擇刷怪塔 → 總時間複雜度 $O(T\sqrt{a+b})$ <font color="yellow">12 / 100</font> ---- ### 刷怪塔 - Subtask 1 $a=0$ → 只會打第二座刷怪塔的怪物 → $b \ge \frac{l(l+1)}{2}$ → 解不等式或二分搜最大值 → 總時間複雜度 $O(T)$ 或 $O(T\log{b})$ <font color="yellow">8 / 100</font> ---- ### 刷怪塔 - Subtask 2 $a=b$ → 觀察到在怪物數量足夠的情況下, 奇數一定選擇第一座刷怪塔,偶數一定選擇第二座 → $a \ge 1+3+\ldots+(2k+1) = (k+1)^2$ $b \ge 2+4+\dots+(2k) = (k+1)^2 + k + 1$ → 解不等式或二分搜最大值 → 總時間複雜度 $O(T)$ 或 $O(T\log{b})$ <font color="yellow">21 / 100</font> ---- ### 刷怪塔 - Subtask 4 將較多的那座擊殺到怪物數量不超過另一座 接下來兩座刷怪塔必定會輪流使用 → 作法類似 Subtask 2 → 總時間複雜度 $O(T)$ 或 $O(T\log{b})$ <font color="lightgreen">100 / 100</font> ---- ```cpp= #include<bits/stdc++.h> using namespace std; signed main() { ios_base::sync_with_stdio(false); cin.tie(0); int T; cin >> T; for (int i = 1; i <= T; i++) { long long A, B; cin >> A >> B; bool swp = (A <= B); if (A < B) swap(A, B); long long l = 0, r = 2e9; while (l != r) { long long m = (l + r) / 2; if (m * (m + 1) / 2 > A - B) r = m; else l = m + 1; } A -= l * (l - 1) / 2; if (A > B || (A == B && !swp)) swap(A, B), swp = !swp; long long x = l - 1; if (B < x + 1) { if (swp) swap(A, B); cout << x << ' ' << A << ' ' << B << '\n'; } else if (A < x + 2) { B -= x + 1; if (swp) swap(A, B); cout << x + 1 << ' ' << A << ' ' << B << '\n'; } else { l = x + 2, r = 2e9; long long Aa = A, Ba = B; while (l != r) { long long m = (l + r) / 2; long long Al = m, Bl = m; if (Al % 2 == x % 2) Bl--; else Al--; long long At = (Al + x + 2) * (Al - x) / 4, Bt = (Bl + x + 1) * (Bl - x + 1) / 4; if (A < At || B < Bt) r = m; else l = m + 1, Aa = A - At, Ba = B - Bt; } if (swp) swap(Aa, Ba); cout << l - 1 << ' ' << Aa << ' ' << Ba << '\n'; } } return 0; } ``` --- <!-- .slide: data-background="https://imgur.com/kjQBXxv.jpg" --> <font color="black"> <font size=7><b> 時空旅人之爭 (Time) </b></font> <font size=6><b> 來源: 原創 </b></font> <font size=6><b> 測資: WiwiHo </b></font> <font size=6><b> 題敘: joylintp </b></font> </font> ---- ## 題目大意 給一棵樹,複製人大軍會從節點 1 開始擴散,每 2 秒擴散一個節點 有 $Q$ 筆詢問,詢問求從入侵的時間點開始,從 $s_i$ 走到 $t_i$ 的路上 至少要經過幾個有複製人大軍的節點 ---- 顯然就是走得越快越好,所以直接走 $s_i$ 到 $t_i$ 的簡單路徑 Kronii 到達 $v$ 的時間:$dis(s_i, v)$ 複製人大軍到達 $v$ 的時間: $2 \times dis(1, v)$ 答案要求有幾個 $v$ 滿足 $dis(s_i,v) \geq 2 \times dis(i,v)$ ---- ## Subtask 1 $n,q \leq 1000$ 暴力檢查 $O(nq)$ ---- ## Subtask 2 $\forall 1 \leq i < n, v_i = u_{i+1}$ 就是這個樹是一條鏈的意思 如果 $s_i,t_i$ 都在 $1$ 的同一側 那麼靠近 $1$ 的那邊可能會有一段連續的符合條件的節點 也就是這條鏈上會有一邊符合條件、一邊不符合 二分搜找交界點 ---- 如果 $s_i,t_i$ 在 $1$ 的兩側 那麼符合條件的節點會是中間連續的一段 如果把 $1$ 左右分開看 就和上一種狀況一樣了 $O(n + q \log n)$ ---- ## Subtask 3 除了節點 1 以外,每個節點度數最多 2 也就是說這個樹是從 1 往外伸的一堆鏈 只看 $s_i$ 和 $t_i$ 所在的鏈 就跟 Subtask 2 一樣了 ---- ## Subtask 4 Subtask 2,3 都是「把 $s_i$ 到 $t_i$ 的路徑,以最靠近 $1$ 的節點為中心把這條路徑切成兩半」,而切出來的兩條路徑上,都是有一邊的點符合條件(靠近中心的那邊)、一邊不符合 $s_i$ 到 $t_i$ 的路徑上離 $1$ 最近的點就是他們的 LCA (以 $1$ 為根) 然後就跟前面一樣了 可以用倍增法二分搜 ---- ```cpp= #include <bits/stdc++.h> #define StarBurstStream ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0); #define eb(a) emplace_back(a) using namespace std; vector<vector<int>> g; vector<int> in, out, dpt; int ts = 0; vector<vector<int>> anc; void dfs(int now, int p, int d){ in[now] = ts++; dpt[now] = d; anc[0][now] = p; for(int i : g[now]){ if(i == p) continue; dfs(i, now, d + 1); } out[now] = ts++; } bool isAnc(int a, int b){ return in[a] <= in[b] && out[a] >= out[b]; } int getLCA(int a, int b){ if(isAnc(a, b)) return a; if(isAnc(b, a)) return b; for(int i = 19; i >= 0; i--){ if(!isAnc(anc[i][a], b)) a = anc[i][a]; } return anc[0][a]; } int getDis(int a, int b){ int lca = getLCA(a, b); return dpt[a] + dpt[b] - 2 * dpt[lca]; } bool check(int s, int now){ return getDis(s, now) < dpt[now] * 2; } void solve(){ int s, t; cin >> s >> t; int lca = getLCA(s, t); if(check(s, lca)){ cout << "0\n"; return; } int ans = 0; int now = s; for(int i = 19; i >= 0; i--){ if(check(s, anc[i][now])) now = anc[i][now]; } ans += dpt[now] - dpt[lca] + !check(s, s); now = t; for(int i = 19; i >= 0; i--){ if(check(s, anc[i][now])) now = anc[i][now]; } ans += dpt[now] - dpt[lca] + !check(s, t); ans--; cout << ans << "\n"; } int main(){ StarBurstStream int n; cin >> n; g.resize(n + 1); in.resize(n + 1); out.resize(n + 1); dpt.resize(n + 1); anc.resize(20, vector<int>(n + 1)); for(int i = 0; i < n - 1; i++){ int u, v; cin >> u >> v; g[u].eb(v); g[v].eb(u); } int q; cin >> q; dfs(1, 1, 0); for(int i = 1; i < 20; i++){ for(int j = 1; j <= n; j++){ anc[i][j] = anc[i - 1][anc[i - 1][j]]; } } for(int i = 0; i < q; i++){ solve(); } return 0; } ```
{"metaMigratedAt":"2023-06-16T08:43:06.692Z","metaMigratedFrom":"YAML","title":"2021 師大附中暑期資訊培訓 模擬競賽 I 題解","breaks":true,"contributors":"[{\"id\":\"6b95215f-91a7-4eaf-bcfe-d43740108f96\",\"add\":8612,\"del\":224},{\"id\":\"02353542-5acb-4a66-abd0-128b1af24abb\",\"add\":5310,\"del\":316}]"}
    938 views