# 模擬競賽 II 題解 ## 2021 師大附中暑期資訊培訓 emanlaicepsa / joylintp / WiwiHo --- <!-- .slide: data-background="https://imgur.com/VJmHRd2.jpg" --> <font color="black"> <font size=7><b> 機器鴨 (Duck) </b></font> <font size=6><b> 來源: 原創 </b></font> <font size=6><b> 測資: WiwiHo </b></font> <font size=6><b> 題敘: joylintp </b></font> </font> ---- ## 題目大意 有兩個序列 $a$、$b$ 從起點開始,你要走 $n$ 步 第 $i$ 步會直線移動 $(a_i,b_i)$ 的距離 把它們重新排列,滿足你走的路線不自交 ---- 如果 $a_i$ 總和或 $b_i$ 總和不是 0 就無解 ---- ## Subtask 1 $n=3$ 如果你隨便弄出 3 個向量 如果這 3 個向量拼不出三角形 唯一的可能就是它們互相平行 否則,這 3 個向量可以以任意順序排列 枚舉所有 $a$ 的排列 檢查每個向量 $(a_i,b_i)$ 是否平行 ---- ## Subtask 2 $n \leq 6$ 枚舉 $a$ 的排列和 $b$ 的排列的搭配 然後檢查能不能當答案 $O(n! \times n! \times n^2)$ ---- ## Subtask 3 $\forall 1 \leq i < n,\ b_i=1$ 如果把 $a$ 由小到大排序 那麼前 $n-1$ 步會走出一個下凸包的形狀 最後一步會直接走回原點 所以這樣走出來的一定是一個凸多邊形 $O(n \log n)$ ---- ## Subtask 4 在 Subtask 1 中 我們用「是不是全部平行」來判斷能不能當解 事實上,只要有 $n$ 個向量 至少有一對不平行 那就可以用它們做出一個合法路線 ---- 在 Subtask 3 中我們做出了一個凸多邊形 只要把這 $n$ 個向量極角排序就可以得到凸多邊形了 ---- 剩下的問題就是怎麼找這 $n$ 個向量 配出 $n$ 個全部平行的向量的方法數 遠低於總方案數 random 會是好的 ---- 可不可以不要 random? 官解作法: 把 $a$ 按絕對值由小到大排序 $b$ 按絕對值由大到小排序 ---- 對於 $|a_i| < |a_j|$ 肯定有 $\frac{|a_i|}{|b_i|} \neq \frac{|a_j|}{|b_j|}$ 因為 $|b_i| \geq |b_j|$ 得出 $\left\lvert\frac{a_i}{b_i}\right\rvert \neq \left\lvert\frac{a_j}{b_j}\right\rvert$ 至於 $|a_i| = |a_j|$ 因為 $a_i$ 皆相異,這最多只有兩個而已 而 $n \geq 3$ 所以肯定有解 $O(n \log n)$ ---- 一些會錯的解: - 照輸入順序原封不動輸出 - 純 random 輸出 - 把 $a$、$b$ 由小排到大湊向量 - 把 $a$ 由小排到大、$b$ 由大排到小湊向量 ---- 為什麼 $n$ 那麼小? 因為我不會寫 checker ---- ```cpp= #include <bits/stdc++.h> #define StarBurstStream ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0); #define iter(a) a.begin(), a.end() #define mp(a, b) make_pair(a, b) #define F first #define S second using namespace std; typedef long long ll; using pll = pair<ll, ll>; int main(){ StarBurstStream int n; cin >> n; vector<ll> a(n), b(n); ll asum = 0, bsum = 0; for(int i = 0; i < n; i++) cin >> a[i], asum += a[i]; for(int i = 0; i < n; i++) cin >> b[i], bsum += b[i]; if(asum != 0 || bsum != 0){ cout << "No\n"; return 0; } sort(iter(a), [](ll a, ll b){ return abs(a) < abs(b); }); sort(iter(b), [](ll a, ll b){ return abs(a) > abs(b); }); vector<pll> v(n); for(int i = 0; i < n; i++) v[i] = mp(a[i], b[i]); auto comp = [](pll a, pll b){ return atan2(a.S, a.F) < atan2(b.S, b.F); }; sort(iter(v), comp); cout << "Yes\n"; for(int i = 0; i < n; i++){ cout << v[i].F << " " << v[i].S << "\n"; } return 0; } ``` --- <!-- .slide: data-background="https://imgur.com/uZwUVue.jpg" --> <font color="black"> <font size=7><b> 惡地之路 (Highway) </b></font> <font size=6><b> 來源: [109 學年度資訊學科能力競賽臺南一中校內複選 pC](https://toj.tfcis.org/oj/pro/568/) </b></font> <font size=6><b> 測資: emanlaicepsa </b></font> <font size=6><b> 題敘: joylintp </b></font> </font> ---- ## 題目大意 給一張圖 令 $s$ 到節點 $i$ 走 $k$ 步的最短距離是 $d(i,k)$ 對於每個 $i$ 求 $\min \{ d(i,k) \times k \}$ ---- ## Subtask 1,2,3,4 暴力走所有可能路徑 - Subtask 1:最多 $O(n!)$ 條路徑 - Subtask 2:圖是鏈狀,只有 $O(n)$ 條路徑 - Subtask 3:圖是樹,只有 $O(n)$ 條路徑 - Subtask 4:圖是環,只有 $O(n)$ 條路徑 ---- ## Subtask 5,6 把每個節點都複製 $n$ 份 如果本來有一條邊是 $(u,v)$ 那就對所有 $1 \leq i < n$ 蓋一條邊是 $u$ 的第 $i$ 個點到 $v$ 的第 $i+1$ 個點(有向) 還有 $v$ 的第 $i$ 到 $u$ 的第 $i+1$ 這樣走到某個節點的第 $i$ 個點的路徑長度一定是 $i-1$ $n^2$ 個點、$2nm$ 個邊做最短路徑 $O((n^2+m) \log n)$ ---- ## Subtask 7 $dp[i][j]=$ 走 $i$ 步到 $j$ 的最短路 \\[dp[i][j]=\min_{(k,j) \in E} \{ dp[i-1][k] + dis(k,j) \}\\] $O(n(n+m))$ DP ---- ```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) #define mp(a, b) make_pair(a, b) #define F first #define S second using namespace std; typedef long long ll; using pll = pair<ll, ll>; int main(){ StarBurstStream int n, m, s; cin >> n >> m >> s; vector<vector<pll>> g(n + 1); for(int i = 0; i < m; i++){ int u, v, p; cin >> u >> v >> p; g[u].eb(mp(v, p)); g[v].eb(mp(u, p)); } vector<ll> dp(n + 1, 1LL << 60); dp[s] = 0; vector<ll> ans(n + 1, 1LL << 60); ans[s] = 0; for(int i = 1; i <= n; i++){ vector<ll> dp2(n + 1, 1LL << 60); for(int j = 1; j <= n; j++){ for(auto e : g[j]){ dp2[e.F] = min(dp2[e.F], dp[j] + e.S); } } dp = dp2; for(int j = 1; j <= n; j++){ if(dp[j] < (1LL << 60)) ans[j] = min(ans[j], dp[j] * i); } } for(int i = 1; i <= n; i++) cout << ans[i] << " "; cout << "\n"; return 0; } ``` --- <!-- .slide: data-background="https://imgur.com/NdWJqQT.jpg" --> <font color="black"> <font size=7><b> 調色盤 (Palette) </b></font> <font size=6><b> 來源: [2020 全國模擬賽公開組 pD](https://codeforces.com/gym/102891/problem/D) </b></font> <font size=6><b> 測資: WiwiHo </b></font> <font size=6><b> 題敘: joylintp </b></font> </font> ---- ## 題目大意 給你一個序列 $c$ 有 $q$ 筆詢問,每筆詢問給 $l, r$ 求有幾個在 $[l,r]$ 裡的區間 $[L,R]$ 滿足 $[L,R]$ 裡的數字全距不超過 $k$ ---- ## Subtask 1 $k=10^6$ 所有區間的全距都不會超過 $k$ 詢問是 $l,r$ 令 $len=r-l+1$ 答案是 $\frac{len(len+1)}{2}$ ---- ## Subtask 2 $n,q \leq 50$ 對每個詢問 枚舉 $O(n^2)$ 個區間 暴力掃一遍計算全距 $O(n^3q)$ ---- ## Subtask 3 $n,q \leq 500$ 對每個詢問 枚舉區間的左端點 然後枚舉右端點的時候可以一邊算區間內的最大最小值 $O(n^2q)$ ---- ## Subtask 4 $n,q \leq 10^4$ 先找到以每個位置為右界時 區間的左界最遠可以到哪 對每個詢問,枚舉右界 可以知道能取的左界有幾個 $O(nq)$ ---- ## Subtask 5,6 把詢問照右界排序 從左邊掃到右邊 計錄「每個位置當左界時,有幾個目前已經掃過的位置可以當右界」 令這個數字是 $r(i)$ ---- 如果目前掃到的這個位置是 $i$ 它當右界時,最遠的左界在 $l(i)$ 那麼就把 $r(l(i)..i)$ 都加上 1 處理右界在目前位置的詢問 直接詢問 $r(l..r)$ 的和即可 ---- $r(i)$ 維護可以用 BIT $l(i)$ 計算可以用單調隊列 或是二分搜配 Sparse Table 求最大最小值 複雜度是 $O((n+q) \log n)$ 如果你在二分搜的時候是配線段樹 複雜度會是 $O(n \log^2 n + q \log n)$ 可能 TLE,只能過 Subtask 5 ---- ```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) #define pob pop_back() #define pof pop_front() #define mp(a, b) make_pair(a, b) #define F first #define S second using namespace std; typedef long long ll; using pii = pair<int, int>; int lowbit(int x){ return x & -x; } struct BIT{ vector<ll> bit; void modify(int x, ll v){ for(; x < bit.size(); x += lowbit(x)){ bit[x] += v; } } ll query(int x){ ll ans = 0; for(; x > 0; x -= lowbit(x)){ ans += bit[x]; } return ans; } }; struct SUMBIT{ BIT bit1, bit2; void modify(int l, int r, ll v){ if(l > r) return; bit1.modify(l, v); bit1.modify(r + 1, -v); bit2.modify(l, l * v); bit2.modify(r + 1, (r + 1) * (-v)); } ll query(int x){ return (x + 1) * bit1.query(x) - bit2.query(x); } ll query(int l, int r){ return query(r) - query(l - 1); } }; int main(){ StarBurstStream int n, k; cin >> n >> k; SUMBIT bit; bit.bit1.bit.resize(n + 1); bit.bit2.bit.resize(n + 1); vector<int> h(n + 1); for(int i = 1; i <= n; i++) cin >> h[i]; vector<vector<pii>> qry(n + 1); int q; cin >> q; for(int i = 0; i < q; i++){ int l, r; cin >> l >> r; qry[r].eb(mp(l, i)); } deque<int> mx, mn; vector<ll> ans(q); int lp = 1; for(int i = 1; i <= n; i++){ while(!mx.empty() && h[mx.back()] <= h[i]) mx.pob; while(!mn.empty() && h[mn.back()] >= h[i]) mn.pob; mx.eb(i); mn.eb(i); while(h[mx.front()] - h[mn.front()] > k){ lp++; while(mx.front() < lp) mx.pof; while(mn.front() < lp) mn.pof; } bit.modify(lp, i, 1); for(auto p : qry[i]){ int id = p.S, l = p.F; ans[id] = bit.query(l, i); } } for(ll i : ans) cout << i << "\n"; return 0; } ``` --- <!-- .slide: data-background="https://imgur.com/bRPOfq5.jpg" --> <font color="black"> <font size=7><b> 告示牌 (Sign) </b></font> <font size=6><b> 來源: [CF 803D](https://codeforces.com/problemset/problem/803/D) </b></font> <font size=6><b> 測資: joylintp </b></font> <font size=6><b> 題敘: joylintp </b></font> </font> ---- ### 告示牌 #### 只有在空格出現時才能換行 ---- ### 告示牌 - Subtask 1 $s$ 沒有空格 → 不能換行 → 答案為 $|s|$ <font color="yellow">2 / 100</font> ---- ### 告示牌 - Subtask 2 $|s| \le 20$ → 枚舉換行的位置 → 複雜度 $O(2^{|s|} \times |s|)$ <font color="yellow">9 / 100</font> ---- ### 告示牌 - Subtask 3 $|s| \le 1000$ → 枚舉告示牌寬度 → 找到最小的 $w$ 能塞得下 → 複雜度 $O(|s|^2)$ <font color="yellow">(9 + 16) / 100</font> ---- ### 告示牌 - Subtask 4 $|s| \le 2 \times 10^5$ ~~→ 枚舉告示牌寬度~~ → 對告示牌寬度二分搜 → 找到最小的 $w$ 能塞得下 → 複雜度 $O(|s|\log{|s|})$ <font color="lightgreen">100 / 100</font> ---- ```cpp= #include<bits/stdc++.h> using namespace std; int k; vector<int> v; bool chk(int w) { int now = 0, cnt = 1; for (int i : v) if (now + i > w) now = i, cnt++; else now += i; return (cnt <= k); } signed main() { ios_base::sync_with_stdio(false); cin.tie(0); cin >> k, cin.ignore(100, '\n'); string s; getline(cin, s); int mx = 0, sum = s.size(); v.clear(); for (int i = 0; i < s.size(); i++) { int tl = 0; while (i < s.size() && s[i] != ' ') tl++, i++; if (i < s.size()) tl++; v.push_back(tl), mx = max(mx, tl); } int l = mx - 1, r = sum; while (l + 1 != r) { int m = (l + r) / 2; if (chk(m)) r = m; else l = m; } cout << r << '\n'; return 0; } ``` --- <!-- .slide: data-background="https://imgur.com/D10N7XN.jpg" --> <font color="black"> <font size=7><b> 變色蠑螈 (UPRP) </b></font> <font size=6><b> 來源: [CF 939D](https://codeforces.com/problemset/problem/939/D) </b></font> <font size=6><b> 測資: joylintp </b></font> <font size=6><b> 題敘: joylintp </b></font> </font> ---- ### 變色蠑螈 - Subtask 1 $c_1 = c_2 = \ldots = c_n$ → 需要的魔法只會有 $c_1$ 到 $d_i$ → 答案是 $d_i$ 的相異數字數量 (? ---- ### 變色蠑螈 - Subtask 1 $c_1 = c_2 = \ldots = c_n$ → 需要的魔法只會有 $c_1$ 到 $d_i$ → 答案是 $d_i$ 的相異數字數量 → 如果存在 $c_1 = d_i$ 記得要減一 `set` 維護,總複雜度 $O(n\log{n})$ <font color="yellow">6 / 100</font> ---- ### 變色蠑螈 - Subtask 2 $a_i,b_i \le 2$ → 只會需要 $1$ 變成 $2$ 的魔法 → 如果存在 $c_i \ne d_i$ 答案為 $1$;否則為 $0$ 複雜度 $O(n)$ <font color="yellow">4 / 100</font> ---- ### 變色蠑螈 蠑螈顏色 → 點 魔法 → 邊 → 判斷需要多少邊才能讓每組 $c_i,d_i$ 連通 ---- ### 變色蠑螈 - Subtask 3 $a_i,b_i \le 4$ → 魔法數量只有 $C^4_2 = 6$ 種 → 枚舉需要哪些魔法 <font color="yellow">9 / 100</font> ---- ### 變色蠑螈 - Subtask 4 $n,c_i \le 1000$ → 每次 DFS 判斷目前的 $c_i,d_i$ 是否已連通 → 不連通就加邊 總複雜度 $O(n^2)$ <font color="yellow">17 / 100</font> ---- ### 變色蠑螈 - Subtask 5 $c_i \le 2 \times 10^5$ ~~→ 每次 DFS 判斷目前的 $c_i,d_i$ 是否已連通~~ → 並查集判斷目前的 $c_i,d_i$ 是否已連通 → 不連通就加邊 總複雜度 $O(n \times \alpha(n))$ <font color="yellow">(17+31) / 100</font> ---- ### 變色蠑螈 - Subtask 6 顏色最多 $2n$ 種 → 先將顏色編號離散化 → 剩下跟 Subtask 5 一樣 總複雜度 $O(n \log{n} + n \times \alpha(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 vector<int> rt; int fnd(int a) { if (a == rt[a]) return a; else return rt[a] = fnd(rt[a]); } void uni(int a, int b) { int x = fnd(a), y = fnd(b); rt[x] = y; } signed main() { ios_base::sync_with_stdio(false); cin.tie(0); int n; cin >> n; vector<int> c(n), d(n), v; for (int &i : c) cin >> i, v.push_back(i); for (int &i : d) cin >> i, v.push_back(i); sort(v.begin(), v.end()), v.resize(unique(v.begin(), v.end()) - v.begin()); rt.resize(v.size()); for (int i = 0; i < rt.size(); i++) rt[i] = i; vector<int> nc(n), nd(n); for (int i = 0; i < n; i++) nc[i] = lower_bound(v.begin(), v.end(), c[i]) - v.begin(); for (int i = 0; i < n; i++) nd[i] = lower_bound(v.begin(), v.end(), d[i]) - v.begin(); vector<pair<int, int>> ans; for (int i = 0; i < n; i++) if (fnd(nc[i]) != fnd(nd[i])) ans.push_back(MP(v[nc[i]], v[nd[i]])), uni(nc[i], nd[i]); cout << ans.size() << '\n'; for (auto p : ans) cout << p.F << ' ' << p.S << '\n'; return 0; } ```
{"metaMigratedAt":"2023-06-16T08:47:39.241Z","metaMigratedFrom":"YAML","title":"2021 師大附中暑期資訊培訓 模擬競賽 II 題解","breaks":true,"contributors":"[{\"id\":\"02353542-5acb-4a66-abd0-128b1af24abb\",\"add\":6838,\"del\":55},{\"id\":\"6b95215f-91a7-4eaf-bcfe-d43740108f96\",\"add\":21330,\"del\":15797}]"}
    624 views
   owned this note