# TOI練習賽202205潛力組 ### [第一題](https://zerojudge.tw/ShowProblem?problemid=q648) #### 考點:貪心 #### 實作方法 ##### 子題一 觀察`n`$\le10$,暴力窮舉每個人是否加入並檢查是否合法。時複雜度度$O({2^n})$ ::: spoiler Python 30% code ```python= from sys import setrecursionlimit setrecursionlimit(1<<30) def DFS(level,now): global ans if level>=n: if now and min(now)>=len(now):ans=max(ans,len(now)) return DFS(level+1,now) DFS(level+1,now+[nums[level]]) ans=0 n=int(input()) nums=list(map(int,input().split())) DFS(0,[]) print(ans) ``` ::: ::: spoiler C++ 30% code ```cpp= #include <bits/stdc++.h> using namespace std; int ans = 0; int n; int nums[200001]; void DFS(int level, vector<int>& now) { if (level >= n) { if (!now.empty()) { int minVal = *min_element(now.begin(), now.end()); if (minVal >= (int)now.size()) { ans = max(ans, (int)now.size()); } } return; } DFS(level + 1, now); now.push_back(nums[level]); DFS(level + 1, now); now.pop_back(); } int main() { cin >> n; for (int i = 0; i < n; i++) {cin >> nums[i];} vector<int> now; DFS(0, now); cout << ans << endl; return 0; } ``` ::: ##### AC 貪心準則:`a[i]`越大的人先加入。 交換論證:假設某個最優解沒有包含一位`a[i]`較大的員工,但包含了一位`a[j]`較小的。 交換 `j`$\rightarrow$`i`: 因為 `a[i]` > `a[j]`,對總人數的限制更寬鬆 所以交換後的新組合會滿足所有限制條件 這樣能產生與原來一樣大小的組合,甚至有可能讓更多人加入 所以:若存在更大 `a[i]` 沒選進來,就一定能優化原解 → 與最優假設矛盾 因此我們貪心選取`a[i]` 較大的員工是正確的。 實作方法:排序`a`(小到大),從左到右檢查容忍數`a[i]`是否$\ge$人數`n-i`。 時間複雜度$O(n log n +n)$ :::spoiler Python AC(0.3s, 30.3MB) code ```python= n=int(input()) nums=sorted(list(map(int,input().split()))) for i in range(n): if nums[i]>=n-i:#現在有n-i個人,目前剛好可以接受,即為答案,越後面答案(n-i)越小 print(n-i) break ``` ::: ::: spoiler C++ AC(0.2s, 1.1MB) code ```cpp= #include <bits/stdc++.h> using namespace std; int main() { int n; cin >> n; vector<int> nums(n); for (int i = 0; i < n; i++) {cin >> nums[i];} sort(nums.begin(), nums.end()); for (int i = 0; i < n; i++) { if (nums[i] >= n - i) { cout << (n - i) << endl;break; } } return 0; } ``` ::: ### [第二題](https://zerojudge.tw/ShowProblem?problemid=q649) #### 考點:二分搜、數學 #### 實作方法 ##### 子題一 觀察`n`$\le3$,直接判斷答案 `n=1`答案即為$m$ `n=2`答案即為$\lfloor \frac{m}{2} \rfloor$+($m\bmod 2$) 說明:先把每個位置設定成$\lfloor \frac{m}{2} \rfloor$,如果多1個($m\bmod 2$),答案可增加1,並且$m\ge n$,故不會有`m=1`的問題。 `n=3` `k=1 or 3`答案即為$\lfloor \frac{m}{3} \rfloor$+$\left[ m\ge4\right]$,$\left[\right]$表[艾佛森括號](https://https://zh.wikipedia.org/zh-tw/%E8%89%BE%E4%BD%9B%E6%A3%AE%E6%8B%AC%E5%8F%B7) 說明:在`m=3`,答案只能是1(因為每個高度必$\ge$ 1) `m>3`,令`x`為答案,步道可表示為`[x+1,x,x-1]`(1和3是對稱),明顯得出總和是`3x`,求出`x+1`即為答案。 `k=2`答案即為$\lfloor \frac{m}{3} \rfloor$+$\left[ m\bmod 3 \ge1\right]$,$\left[\right]$表[艾佛森括號](https://https://zh.wikipedia.org/zh-tw/%E8%89%BE%E4%BD%9B%E6%A3%AE%E6%8B%AC%E5%8F%B7) 令`x`為答案,步道可表示為`[x,x,x]`,總和即為`3x`,如果還有剩餘就加上1。 :::spoiler Python 10% code ```python= for _ in range(int(input())): ans=0 n,m,k=map(int,input().split()) if n==1:ans=m elif n==2:ans=m//2+(m%2) else: if (k==1 or k==3):ans=m//3+int(m>3) else:ans=m//3+int(m%3>0) print(ans) ``` ::: ::: spoiler C++ 10% code ```cpp= #include <bits/stdc++.h> using namespace std; int main() { int t; cin >> t; while (t--) { int n, m, k, ans = 0; cin >> n >> m >> k; if (n == 1) ans = m; else if (n == 2) ans = m / 2 + (m % 2); else { if (k == 1 || k == 3) ans = m / 3 + (m > 3 ? 1 : 0); else ans = m / 3 + (m % 3 > 0 ? 1 : 0); } cout << ans << endl; } return 0; } ``` ::: ##### 子題二 模擬每次答案增加1的情況,直到`m<0`,輸出答案-1。時間複雜度大約$O(T*\sqrt{m})$ :::spoiler Python 40% code ```python= for _ in range(int(input())): n,m,k=map(int,input().split()) ans=1#每個至少為1 m-=n#每個至少為1 l_now=0#儲存左界有幾個數字要增加 r_now=0#儲存右界有幾個數字要增加 while(m>=0): ans+=1 m-=(1+l_now+r_now) l_now=min(l_now+1,k-1) r_now=min(r_now+1,n-k) print(ans-1) ``` ::: ::: spoiler C++ 85% code ```cpp= #include <bits/stdc++.h> using namespace std; int main() { int t; cin >> t; while (t--) { int n, m, k; cin >> n >> m >> k; int ans = 1;//每個至少為1 m -= n;//每個至少為1 int l_now = 0, r_now = 0;//儲存左、右界有幾個數字要增加 while (m >= 0) { ans += 1; m -= (1 + l_now + r_now); l_now = min(l_now + 1, k - 1); r_now = min(r_now + 1, n - k); } cout << ans - 1 << endl; } return 0; } ``` ::: ##### AC 預處理每個至少為1,故`m-=n` 用二分搜枚舉答案,檢查總和是否超過`m` 其中左標(小)至少$\ge$$\lfloor \frac{m}{n} \rfloor$,右標(大)最多$m$ 計算公式:$((L + mid) * (mid - L + 1) + (mid + R - 1) * (mid - R))$(By [M_SQRT](https://zerojudge.tw/UserStatistic?id=195452)(就是出題者)) 時間複雜度$O(T * log m)$ :::spoiler ~~一開始我寫的噁爛判斷~~ ```python= #沒有預處理m-=n total=now total+=now*(now-1) if now-(k-1)<1:total+=abs(k-now) else:total-=abs(now-k+1)*abs(now-k)//2 if now-(n-k)<1:total+=abs(n-k-now+1) else:total-=abs(now-(n-k))*abs(now-n+k-1)//2 ``` ::: :::spoiler Python 85% code ```python= def check(X, N, K): L = max(X - K + 1, 0) R = max(X - N + K, 0) return ((L + X) * (X - L + 1) + (X + R - 1) * (X - R)) // 2 def solve(): n,m,k=map(int,input().split()) m-=n left,right=m//n,m while(left<=right): mid=(left+right)//2 if check(mid,n,k)<=m:left=mid+1 else: right=mid-1 print(left) for _ in range(int(input())):solve() ``` ::: :::spoiler C++ AC(0.5s, 320KB) code ```cpp= #include <bits/stdc++.h> using namespace std; typedef long long ll; ll check(ll X, ll N, ll K) { ll L = max(X - K + 1, 0LL); ll R = max(X - N + K, 0LL); return ((L + X) * (X - L + 1LL) + (X + R - 1LL) * (X - R)) / 2LL; } void solve() { ll n, m, k; cin >> n >> m >> k; m -= n; ll left = m / n, right = m; while (left <= right) { ll mid = (left + right) / 2; if (check(mid, n, k) <= m) left = mid + 1; else right = mid - 1; } cout << left << endl; } int main() { int t; cin >> t; while (t--) solve(); return 0; } ``` ::: ##### AC(2) 你以為Python AC無望,~~太天真了~~。 由[M_SQRT](https://zerojudge.tw/UserStatistic?id=195452)大大導出的公式解 下圖是我改過的圖片![過程](https://hackmd.io/_uploads/Sk5H0JPfgl.png) `if` 用來判斷哪個步驟開始,`m`不夠繼續。時間複雜度$O(T)$。 :::spoiler Python AC(1s,3.4MB) code ```python= for _ in range(int(input())): n,m,k = map(int, input().split()) if k>n//2:k=n-k+1 m-=n if m<k*k: print(int(m**0.5)+1) continue m-=k*k temp=(n+2*k)*(n-2*k+1)//2 if m<temp: print(int(((8 * (k * (2 * k - 1) + m) + 1)**0.5 - 1) / 2) - k + 2) continue m -= temp print(m//n+n-k+2) ``` ::: ::: spoiler Python AC(0.4s,7.9MB) code(I/O優化) ```python= def main(): from sys import stdin,stdout input=stdin.readline print=stdout.write for _ in range(int(input())): n,m,k = map(int, input().split()) if k>n//2:k=n-k+1 m-=n if m<k*k: print(str(int(m**0.5)+1)+"\n") continue m-=k*k temp=(n+2*k)*(n-2*k+1)//2 if m<temp: print(str(int(((8 * (k * (2 * k - 1) + m) + 1)**0.5 - 1) / 2) - k + 2)+"\n") continue m -= temp print(str(m//n+n-k+2)+"\n") main() ``` ::: ::: spoiler C++ AC(0.5s, 328KB) code ```cpp= #include <bits/stdc++.h> using namespace std; typedef long long ll; int main() { int t; cin >> t; while (t--) { ll n, m, k; cin >> n >> m >> k; if (k > n / 2) k = n - k + 1; m -= n; if (m < k * k) { cout << (ll)(sqrt(m) + 1) << endl; continue; } m -= k * k; ll temp = (n + 2 * k) * (n - 2 * k + 1) / 2; if (m < temp) { ll val = (8 * (k * (2 * k - 1) + m) + 1); cout << (ll)((sqrt(val) - 1) / 2) - k + 2 << endl; continue; } m -= temp; cout << m / n + n - k + 2 << endl; } return 0; } ``` ::: ::: spoiler C++ AC(88ms, 356KB) code(I/O優化) ```cpp= #include <bits/stdc++.h> using namespace std; typedef long long ll; int main() { ios::sync_with_stdio(false); cin.tie(0); int t; cin >> t; while (t--) { ll n, m, k; cin >> n >> m >> k; if (k > n / 2) k = n - k + 1; m -= n; if (m < k * k) { cout << (ll)(sqrt(m) + 1) << '\n'; continue; } m -= k * k; ll temp = (n + 2 * k) * (n - 2 * k + 1) / 2; if (m < temp) { ll val = 8 * (k * (2 * k - 1) + m) + 1; cout << (ll)((sqrt(val) - 1) / 2) - k + 2 << '\n'; continue; } m -= temp; cout << m / n + n - k + 2 << '\n'; } return 0; } ``` ::: ::: spoiler C++ AC(62ms, 336KB) code(scanf,printf) ```cpp= #include <bits/stdc++.h> using namespace std; typedef long long ll; int main() { int t; scanf("%d", &t); while (t--) { ll n, m, k; scanf("%lld %lld %lld", &n, &m, &k); if (k > n / 2) k = n - k + 1; m -= n; if (m < k * k) { printf("%lld\n", (ll)(sqrt(m) + 1)); continue; } m -= k * k; ll temp = (n + 2 * k) * (n - 2 * k + 1) / 2; if (m < temp) { ll val = 8 * (k * (2 * k - 1) + m) + 1; printf("%lld\n", (ll)((sqrt(val) - 1) / 2) - k + 2); continue; } m -= temp; printf("%lld\n", m / n + n - k + 2); } return 0; } ``` ::: ### [第三題](https://zerojudge.tw/ShowProblem?problemid=q650) #### 考點:Dijkstra #### 實作方法 ##### 子題一 觀察`n`$\le10$,暴力BFS ::: spoiler Python 35% code ```python= import sys from collections import deque def min_refuel_wait_time_sub1(N, M, F, wait_times, roads): graph = [[] for _ in range(N+1)] for a,b,c in roads: graph[a].append((b,c)) graph[b].append((a,c)) # dist[city][fuel] = 最少等待時間 dist = [[float('inf')] * (F+1) for _ in range(N+1)] dist[1][F] = 0 queue = deque() queue.append((1, F)) while queue: city, fuel = queue.popleft() curr_wait = dist[city][fuel] if city == N: return curr_wait # 在城市加油(如果不是起點或終點) if city != 1 and city != N: new_wait = curr_wait + wait_times[city] if dist[city][F] > new_wait: dist[city][F] = new_wait queue.append((city, F)) # 走鄰居,消耗油量 for nxt, cost in graph[city]: if fuel >= cost: nfuel = fuel - cost if dist[nxt][nfuel] > curr_wait: dist[nxt][nfuel] = curr_wait queue.append((nxt, nfuel)) return -1 def main(): input = sys.stdin.read data = input().split() idx=0 N = int(data[idx]); idx+=1 M = int(data[idx]); idx+=1 F = int(data[idx]); idx+=1 wait_input = list(map(int, data[idx:idx+N-2])) idx += N-2 wait_times = [0]*(N+1) for i in range(2, N): wait_times[i] = wait_input[i-2] roads = [] for _ in range(M): a,b,c = int(data[idx]), int(data[idx+1]), int(data[idx+2]) idx+=3 roads.append((a,b,c)) ans = min_refuel_wait_time_sub1(N,M,F,wait_times,roads) print(ans) if __name__ == "__main__": main() ``` ::: :::spoiler C++ 40% code ```cpp= #include <bits/stdc++.h> using namespace std; int min_refuel_wait_time_sub1(int N, int M, int F, vector<int>& wait_times, vector<tuple<int,int,int>>& roads) { vector<vector<pair<int,int>>> graph(N+1); for (auto& e : roads) { int a,b,c; tie(a,b,c) = e; graph[a].emplace_back(b,c); graph[b].emplace_back(a,c); } vector<vector<int>> dist(N+1, vector<int>(F+1, INT_MAX)); dist[1][F] = 0; deque<pair<int,int>> queue; queue.emplace_back(1, F); while (!queue.empty()) { auto [city, fuel] = queue.front(); queue.pop_front(); int curr_wait = dist[city][fuel]; if (city == N) return curr_wait; if (city != 1 && city != N) { int new_wait = curr_wait + wait_times[city]; if (dist[city][F] > new_wait) { dist[city][F] = new_wait; queue.emplace_back(city, F); } } for (auto& [nxt, cost] : graph[city]) { if (fuel >= cost) { int nfuel = fuel - cost; if (dist[nxt][nfuel] > curr_wait) { dist[nxt][nfuel] = curr_wait; queue.emplace_back(nxt, nfuel); } } } } return -1; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int N, M, F; cin >> N >> M >> F; vector<int> wait_times(N+1, 0); for (int i = 2; i <= N-1; i++) { cin >> wait_times[i]; } vector<tuple<int,int,int>> roads(M); for (int i = 0; i < M; i++) { int a,b,c; cin >> a >> b >> c; roads[i] = make_tuple(a,b,c); } cout << min_refuel_wait_time_sub1(N, M, F, wait_times, roads) << "\n"; return 0; } ``` ::: ##### 子題二 簡化等待時間加權 Dijkstra(只算加油次數) :::spoiler Python 35% code ```python= import sys import heapq from collections import defaultdict def min_refuel_wait_time_sub2(N, M, F, wait_times, roads): # 所有ti=1時,可以將等待時間當成加油次數 (等價於1) graph = defaultdict(list) for a,b,c in roads: graph[a].append((b,c)) graph[b].append((a,c)) def dijkstra_from(city): dist = [float('inf')] * (N+1) dist[city] = 0 pq = [(0, city)] reachable = [] while pq: d,u = heapq.heappop(pq) if d > F: continue reachable.append((u,d)) for v,w in graph[u]: nd = d + w if nd <= F and nd < dist[v]: dist[v] = nd heapq.heappush(pq,(nd,v)) return reachable min_wait = [float('inf')] * (N+1) min_wait[1] = 0 pq = [(0,1)] while pq: curr_wait, city = heapq.heappop(pq) if city == N: return curr_wait if curr_wait > min_wait[city]: continue for next_city, dist in dijkstra_from(city): if next_city == city: continue # ti = 1 for all i wtime = 1 if next_city != 1 and next_city != N else 0 new_wait = curr_wait + wtime if new_wait < min_wait[next_city]: min_wait[next_city] = new_wait heapq.heappush(pq, (new_wait, next_city)) return -1 def main(): input=sys.stdin.read data = input().split() idx=0 N = int(data[idx]); idx+=1 M = int(data[idx]); idx+=1 F = int(data[idx]); idx+=1 wait_input = list(map(int, data[idx:idx+N-2])) idx += N-2 wait_times = [0]*(N+1) for i in range(2, N): wait_times[i] = wait_input[i-2] roads = [] for _ in range(M): a,b,c = int(data[idx]), int(data[idx+1]), int(data[idx+2]) idx+=3 roads.append((a,b,c)) ans = min_refuel_wait_time_sub2(N,M,F,wait_times,roads) print(ans) if __name__ == "__main__": main() ``` ::: ::: spoiler C++ 35% code ```cpp= #include <bits/stdc++.h> using namespace std; int min_refuel_wait_time_sub2(int N, int M, int F, vector<int>& wait_times, vector<tuple<int,int,int>>& roads) { vector<vector<pair<int,int>>> graph(N+1); for (auto& e : roads) { int a,b,c; tie(a,b,c) = e; graph[a].emplace_back(b,c); graph[b].emplace_back(a,c); } auto dijkstra_from = [&](int city) { vector<int> dist(N+1, INT_MAX); dist[city] = 0; priority_queue<pair<int,int>, vector<pair<int,int>>, greater<>> pq; pq.emplace(0, city); vector<pair<int,int>> reachable; while (!pq.empty()) { auto [d,u] = pq.top(); pq.pop(); if (d > F) continue; reachable.emplace_back(u,d); for (auto& [v,w] : graph[u]) { int nd = d + w; if (nd <= F && nd < dist[v]) { dist[v] = nd; pq.emplace(nd, v); } } } return reachable; }; vector<int> min_wait(N+1, INT_MAX); min_wait[1] = 0; priority_queue<pair<int,int>, vector<pair<int,int>>, greater<>> pq; pq.emplace(0, 1); while (!pq.empty()) { auto [curr_wait, city] = pq.top(); pq.pop(); if (city == N) return curr_wait; if (curr_wait > min_wait[city]) continue; for (auto& [next_city, dist] : dijkstra_from(city)) { if (next_city == city) continue; int wtime = (next_city != 1 && next_city != N) ? 1 : 0; int new_wait = curr_wait + wtime; if (new_wait < min_wait[next_city]) { min_wait[next_city] = new_wait; pq.emplace(new_wait, next_city); } } } return -1; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int N, M, F; cin >> N >> M >> F; vector<int> wait_times(N+1, 0); for (int i = 2; i <= N-1; i++) { cin >> wait_times[i]; } vector<tuple<int,int,int>> roads(M); for (int i = 0; i < M; i++) { int a,b,c; cin >> a >> b >> c; roads[i] = make_tuple(a,b,c); } cout << min_refuel_wait_time_sub2(N, M, F, wait_times, roads) << "\n"; return 0; } ``` ::: ##### AC 用同樣暴力加油Dijkstra,考慮不同等待時間 ::: spoiler Python AC(2.4s, 11.2MB) code ``` python= import sys import heapq from collections import defaultdict def min_refuel_wait_time_full(N, M, F, wait_times, roads): graph = defaultdict(list) for a,b,c in roads: graph[a].append((b,c)) graph[b].append((a,c)) def dijkstra_from(city): dist = [float('inf')] * (N+1) dist[city] = 0 pq = [(0, city)] reachable = [] while pq: d,u = heapq.heappop(pq) if d > F: continue reachable.append((u,d)) for v,w in graph[u]: nd = d + w if nd <= F and nd < dist[v]: dist[v] = nd heapq.heappush(pq,(nd,v)) return reachable min_wait = [float('inf')] * (N+1) min_wait[1] = 0 pq = [(0,1)] while pq: curr_wait, city = heapq.heappop(pq) if city == N: return curr_wait if curr_wait > min_wait[city]: continue for next_city, dist in dijkstra_from(city): if next_city == city: continue wtime = wait_times[next_city] new_wait = curr_wait + wtime if new_wait < min_wait[next_city]: min_wait[next_city] = new_wait heapq.heappush(pq, (new_wait, next_city)) return -1 def main(): input=sys.stdin.read data = input().split() idx=0 N = int(data[idx]); idx+=1 M = int(data[idx]); idx+=1 F = int(data[idx]); idx+=1 wait_input = list(map(int, data[idx:idx+N-2])) idx += N-2 wait_times = [0]*(N+1) for i in range(2, N): wait_times[i] = wait_input[i-2] roads = [] for _ in range(M): a,b,c = int(data[idx]), int(data[idx+1]), int(data[idx+2]) idx+=3 roads.append((a,b,c)) ans = min_refuel_wait_time_full(N,M,F,wait_times,roads) print(ans) if __name__ == "__main__": main() ``` ::: ::: spoiler C++ AC(20ms, 1.1MB) code ```cpp= #include <bits/stdc++.h> using namespace std; int min_refuel_wait_time_full(int N, int M, int F, vector<int>& wait_times, vector<tuple<int,int,int>>& roads) { vector<vector<pair<int,int>>> graph(N+1); for (auto& e : roads) { int a,b,c; tie(a,b,c) = e; graph[a].emplace_back(b,c); graph[b].emplace_back(a,c); } auto dijkstra_from = [&](int city) { vector<int> dist(N+1, INT_MAX); dist[city] = 0; priority_queue<pair<int,int>, vector<pair<int,int>>, greater<>> pq; pq.emplace(0, city); vector<pair<int,int>> reachable; while (!pq.empty()) { auto [d,u] = pq.top(); pq.pop(); if (d > F) continue; reachable.emplace_back(u,d); for (auto& [v,w] : graph[u]) { int nd = d + w; if (nd <= F && nd < dist[v]) { dist[v] = nd; pq.emplace(nd, v); } } } return reachable; }; vector<int> min_wait(N+1, INT_MAX); min_wait[1] = 0; priority_queue<pair<int,int>, vector<pair<int,int>>, greater<>> pq; pq.emplace(0, 1); while (!pq.empty()) { auto [curr_wait, city] = pq.top(); pq.pop(); if (city == N) return curr_wait; if (curr_wait > min_wait[city]) continue; for (auto& [next_city, dist] : dijkstra_from(city)) { if (next_city == city) continue; int wtime = wait_times[next_city]; int new_wait = curr_wait + wtime; if (new_wait < min_wait[next_city]) { min_wait[next_city] = new_wait; pq.emplace(new_wait, next_city); } } } return -1; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int N, M, F; cin >> N >> M >> F; vector<int> wait_times(N+1, 0); for (int i = 2; i <= N-1; i++) { cin >> wait_times[i]; } vector<tuple<int,int,int>> roads(M); for (int i = 0; i < M; i++) { int a,b,c; cin >> a >> b >> c; roads[i] = make_tuple(a,b,c); } cout << min_refuel_wait_time_full(N, M, F, wait_times, roads) << "\n"; return 0; } ``` :::