# TOI練習賽202504潛力組
### [第一題](https://zerojudge.tw/ShowProblem?problemid=q377)
#### 考點:貪心
#### 實作方法
##### 子題一
暴力枚舉所有拆除情況,時間複雜度$\mathcal O(n*n!)$
::: spoiler Python 15% code
```python=
from itertools import permutations as P
n=int(input())
ans=float('inf')
mylist=[]
total=0
for i in range(n):
a,b=map(int,input().split())
mylist.append((a,b))
total+=a
for i in P(list(range(n))):
temp=total
cost=0
for j in i:
cost+=temp*mylist[j][1]
temp-=mylist[j][0]
ans=min(ans,cost)
print(ans)
```
:::
:::spoiler C++ 20% code
```cpp=
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
vector<pair<int, int>> mylist;
vector<int> perm(n);
int total = 0;
for (int i = 0; i < n; ++i) {
int a, b;
cin >> a >> b;
mylist.push_back({a, b});
total += a;
perm[i] = i;
}
long long ans = LLONG_MAX;
do {
int temp = total;
long long cost = 0;
for (int j = 0; j < n; ++j) {
int idx = perm[j];
cost += 1LL * temp * mylist[idx].second;
temp -= mylist[idx].first;
}
ans = min(ans, cost);
} while (next_permutation(perm.begin(), perm.end()));
cout << ans << endl;
return 0;
}
```
:::
##### 子題二
觀察到所有工程都只要一天,貪心準則:罰金多的先做。
簡易證明:假設兩個工程罰金分別為$a_1$、$a_2$,第一天固定為$a_1+a_2$,第二天可能是$a_1$或$a_2$。顯然要讓罰金比較小,第二天就要是$\min(a_1,a_2)$,也就是第一天是$\max(a_1,a_2)$,依此類推。
時間複雜度$\mathcal O(nlogn+n)$
::: spoiler Python 20% code(有判斷是否天數皆為1)
```python=
n=int(input())
ans=0
mylist=[]
total=0
flag=1
for i in range(n):
a,b=map(int,input().split())
mylist.append((a,b))
total+=a
flag&=(b==1)
if flag:
mylist.sort(key=lambda x:x[0])
while mylist:
cost,day=mylist.pop()
ans+=total*day
total-=cost
print(ans)
```
:::
::: spoiler C++ 20% code
```cpp=
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, flag = 1;
long long int ans = 0, total = 0;
cin >> n;
vector<pair<int, int>> mylist;
for (int i = 0; i < n; ++i) {
int a, b;
cin >> a >> b;
mylist.push_back({a, b});
total += a;
flag &= (b == 1);
}
if (flag) {
sort(mylist.begin(), mylist.end());
while (!mylist.empty()) {
auto [cost, day] = mylist.back();
mylist.pop_back();
ans += total * day;
total -= cost;
}
cout << ans << endl;
}
return 0;
}
```
:::
::: spoiler Python 45% code(省去判斷,增加AC測資數)
```python=
n=int(input())
ans=0
mylist=[]
total=0
for i in range(n):
a,b=map(int,input().split())
mylist.append((a,b))
total+=a
mylist.sort(key=lambda x:x[0])
while mylist:
cost,day=mylist.pop()
ans+=total*day
total-=cost
print(ans)
```
:::
::: spoiler C++ 45% code
```cpp=
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
long long int ans = 0, total = 0;
cin >> n;
vector<pair<int, int>> mylist;
for (int i = 0; i < n; ++i) {
int a, b;
cin >> a >> b;
mylist.push_back({a, b});
total += a;
}
sort(mylist.begin(), mylist.end());
while (!mylist.empty()) {
auto [cost, day] = mylist.back();
mylist.pop_back();
ans += total * day;
total -= cost;
}
cout << ans << endl;
return 0;
}
```
:::
##### 子題三
這次考慮$b$。在拆除時,固定有$a_1*b_1$,$a_2*b_2$的罰金。但是中間會有$a_1*b_2$或$a_2*b_1$的罰金(代表兩棟建築同時存在),選擇較小,也就是每次兩個比較,較大的要先做。時間複雜度$\mathcal O(n^2+n)$
:::spoiler Python 50% code
```python=
n = int(input())
ans = 0
mylist = []
total = 0
flag=1
for i in range(n):
a, b = map(int, input().split())
mylist.append((a, b))
total += a
flag&=(b==1)
if n<=100: #因為zero judge的設計,不加這行可能會在前幾筆就TLE然後不能跑後面的測資
for i in range(n):
for j in range(0, n - i - 1):
a = mylist[j]
b = mylist[j + 1]
if a[0] * b[1] > b[0] * a[1]:
mylist[j], mylist[j + 1] = mylist[j + 1], mylist[j]
while mylist:
cost, day = mylist.pop()
ans += total * day
total -= cost
print(ans)
```
:::
:::spoiler C++ 50% code
```cpp=
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, flag = 1;
long long int ans = 0, total = 0;
cin >> n;
vector<pair<int, int>> mylist;
for (int i = 0; i < n; ++i) {
int a, b;
cin >> a >> b;
mylist.push_back({a, b});
total += a;
flag &= (b == 1);
}
if (n <= 100) {//因為zero judge的設計,不加這行可能會在前幾筆就TLE然後不能跑後面的測資
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n - i - 1; ++j) {
auto a = mylist[j];
auto b = mylist[j + 1];
if (1LL * a.first * b.second > 1LL * b.first * a.second) {
swap(mylist[j], mylist[j + 1]);
}
}
}
while (!mylist.empty()) {
auto [cost, day] = mylist.back();
mylist.pop_back();
ans += total * day;
total -= cost;
}
cout << ans << endl;
}
return 0;
}
```
:::
##### AC
將上述排序依據($a_1*b_2$,$a_2*b_1$)同除以($b_1*b_2$)得到($a_1/b_1$,$a_2/b_2$),這個即可作為Python排序的鍵值。C++則因為浮點數精度誤差,故不必變成除法。使用內建排序,時間複雜度$\mathcal O(nlogn+n)$
:::spoiler Python AC(1.1s, 39.3MB) code
```python=
n=int(input())
ans=0
mylist=[]
total=0
for i in range(n):
a,b=map(int,input().split())
mylist.append((a,b))
total+=a
mylist.sort(key=lambda x:x[0]/x[1])
while mylist:
cost,day=mylist.pop()
ans+=total*day
total-=cost
print(ans)
```
:::
::: spoiler C++ AC(0.3s,3.9MB) code
```cpp=
#include <bits/stdc++.h>
using namespace std;
bool cmp(pair<int, int> a, pair<int, int> b) {
return a.first * b.second < b.first * a.second;
}
int main() {
int n;
long long int ans = 0, total = 0;
cin >> n;
vector<pair<int, int>> mylist;
for (int i = 0; i < n; ++i) {
int a, b;
cin >> a >> b;
mylist.push_back(make_pair(a, b));
total += a;
}
sort(mylist.begin(), mylist.end(), cmp);
while (!mylist.empty()) {
auto [cost, day] = mylist.back();
mylist.pop_back();
ans += total * day;
total -= cost;
}
cout <<ans << endl;
return 0;
}
```
:::
:::spoiler C++ AC(0.3s,3.9MB) code(除法比較)
```cpp=
#include <bits/stdc++.h>
using namespace std;
bool cmp(pair<double, double> a, pair<double, double> b) {
double c=a.first/a.second,d=b.first/b.second;
return c<d;
}
int main() {
int n;
long long int ans = 0, total = 0;
cin >> n;
vector<pair<int, int>> mylist;
for (int i = 0; i < n; i++) {
int a, b;
cin >> a >> b;
mylist.push_back({a, b});
total += a;
}
sort(mylist.begin(), mylist.end(), cmp);
while (!mylist.empty()) {
auto [cost, day] = mylist.back();
mylist.pop_back();
ans += total * day;
total -= cost;
}
cout <<ans << endl;
return 0;
}
```
:::
### [第二題](https://zerojudge.tw/ShowProblem?problemid=q378)
#### 考點:DP
#### 實作方法
##### 子題一
暴力窮舉在長度為`now`時,共有幾種組合。把每個組合依序配對。
例如`n=3`,共有`(0),(1),(2),(0,1),(0,2),(1,2),(0,1,2)`七種。
在`now=2`,取出`(0,1),(0,2),(1,2)`兩兩配對為`out`,`in_`的`index`,共有`((0,1),(0,1)),((0,1),(0,2)),((0,2),(1,2)),((0,2),(1,2))`...九種。
以`((0,1),(0,2))`為範例,這個代表`out[0]`接`in_[1]`,`out[1]`接`in_[2]`
由於產生組合的方法是遞增的,故不會出現交叉,再把每組都計算一次得到答案。
時間複雜度$\mathcal O(\sum_{k=1}^{n} [C(n, k)^2 × k])\approx \mathcal O(4^n*n)$,$C$表組合計算公式。
:::spoiler Python 40% code
```python=
from itertools import combinations as C
n=int(input())
out=list(map(int,input().split()))
in_=list(map(int,input().split()))
nums=list(range(n))
ans=-1
for now in range(1,n+1):
com=list(C(nums,now))
for i in com:
for j in com:
cost=0
for s in range(now):
cost+=min(out[i[s]],in_[j[s]])
ans=max(ans,cost)
print(ans)
```
:::
::: spoiler C++ 40% code
```cpp=
#include<bits/stdc++.h>
using namespace std;
int n, ans = -1;
vector<int> out, in_, comb;
void generate_combinations(int start, int depth, int k, vector<vector<int>> &res) {
if(depth == k) {
res.push_back(comb);
return;
}
for(int i = start; i < n; i++) {
comb[depth] = i;
generate_combinations(i + 1, depth + 1, k, res);
}
}
int main() {
cin >> n;
out.resize(n);
in_.resize(n);
for(int i = 0; i < n; i++) cin >> out[i];
for(int i = 0; i < n; i++) cin >> in_[i];
for(int now = 1; now <= n; now++) {
vector<vector<int>> com;
comb.resize(now);
generate_combinations(0, 0, now, com);
for(auto &i : com) {
for(auto &j : com) {
int cost = 0;
for(int s = 0; s < now; s++) {
cost += min(out[i[s]], in_[j[s]]);
}
ans = max(ans, cost);
}
}
}
cout << ans << endl;
return 0;
}
```
:::
##### AC
定義 $dp[i][j]$代表考慮前`i-1`個出水口和前`j-1`個進水口的最大值(不包含`i`,`j`)
枚舉出水口`i`、進水口`j`,考慮連不連接
一、不連接:$dp[i+1][j+1]=max(dp[i][j+1],dp[i+1][j])$
二、連接:$dp[i][j]=dp[i][j]+min(in_[i],out[j])$
合併:$dp[i+1][j+1]=max(dp[i][j+1],dp[i+1][j],dp[i][j]+min(in_[i],out[j]))$
時間複雜度$\mathcal O(n^2)$
:::spoiler Python AC(1.3s, 27.1MB) code
```python=
def DP(n,in_,out):
dp=[[0]*(n+1) for _ in range(n+1)]
for i in range(n):
for j in range(n):
dp[i+1][j+1]=max(dp[i][j+1],dp[i+1][j],dp[i][j]+min(in_[i],out[j]))
return dp[n][n]
n=int(input())
in_=list(map(int,input().split()))
out=list(map(int,input().split()))
print(DP(n,in_,out))
```
:::
:::spoiler C++ AC(8ms, 4.2MB) code
```cpp=
#include <bits/stdc++.h>
using namespace std;
int DP(int n, vector<int>& in_, vector<int>& out) {
vector<vector<int>> dp(n + 1, vector<int>(n + 1, 0));
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
dp[i + 1][j + 1] = max({dp[i][j + 1], dp[i + 1][j], dp[i][j] + min(in_[i], out[j])});
}
}
return dp[n][n];
}
int main() {
int n;
cin >> n;
vector<int> in_(n), out(n);
for (int i = 0; i < n; ++i) cin >> in_[i];
for (int i = 0; i < n; ++i) cin >> out[i];
cout << DP(n, in_, out) << endl;
return 0;
}
```
:::
### [第三題](https://zerojudge.tw/ShowProblem?problemid=q379)
#### 考點:最小生成樹(MST)、二分搜
#### 實作方法
##### 子題一、二
用kruskal建構一個MST計算初始最小成本。
觀察到`Q=1`,讀入一筆資料重新計算。時間複雜度$\mathcal O(MlogM)$
::: spoiler Python 60% code
```python=
import sys
sys.setrecursionlimit(1 << 25)
input = sys.stdin.readline
class UnionFind:
def __init__(self, size):
self.parent = list(range(size))
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y):
xr, yr = self.find(x), self.find(y)
if xr == yr:
return False
self.parent[yr] = xr
return True
def kruskal(N, edge_dict):
uf = UnionFind(N)
edges = sorted([(u, v, w) for (u, v), w in edge_dict.items()], key=lambda x: x[2])
mst_cost = 0
count = 0
for u, v, w in edges:
if uf.union(u, v):
mst_cost += w
count += 1
if count == N - 1:
break
return mst_cost
N, M, Q = map(int, input().split())
edge_dict = {}
for _ in range(M):
S, T, C = map(int, input().split())
edge_dict[(S, T)] = C
Qs, Qt, Qc = map(int, input().split())
K = int(input())
if kruskal(N, edge_dict) > K:
print(0)
else:
if (Qs, Qt) in edge_dict:
edge_dict[(Qs, Qt)] += Qc
else:
edge_dict[(Qs, Qt)] = Qc
if kruskal(N, edge_dict) > K:
print(1)
else:
print(-1)
```
:::
:::spoiler C++ 60% code
```cpp=
#include <bits/stdc++.h>
using namespace std;
class UnionFind {
public:
vector<int> parent;
UnionFind(int size) {
parent.resize(size);
iota(parent.begin(), parent.end(), 0);
}
int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
bool union_sets(int x, int y) {
int xr = find(x), yr = find(y);
if (xr == yr) return false;
parent[yr] = xr;
return true;
}
};
int kruskal(int N, map<pair<int, int>, int>& edge_dict) {
UnionFind uf(N);
vector<tuple<int, int, int>> edges;
for (auto& e : edge_dict) {
edges.push_back({e.second, e.first.first, e.first.second});
}
sort(edges.begin(), edges.end());
int mst_cost = 0, count = 0;
for (auto& [w, u, v] : edges) {
if (uf.union_sets(u, v)) {
mst_cost += w;
count++;
if (count == N - 1) break;
}
}
return mst_cost;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int N, M, Q;
cin >> N >> M >> Q;
map<pair<int, int>, int> edge_dict;
for (int i = 0; i < M; i++) {
int S, T, C;
cin >> S >> T >> C;
edge_dict[{S, T}] = C;
}
int Qs, Qt, Qc;
cin >> Qs >> Qt >> Qc;
int K;
cin >> K;
if (kruskal(N, edge_dict) > K) {
cout << 0 << '\n';
return 0;
} else {
if (edge_dict.count({Qs, Qt})) {
edge_dict[{Qs, Qt}] += Qc;
} else {
edge_dict[{Qs, Qt}] = Qc;
}
if (kruskal(N, edge_dict) > K) {
cout << 1 << '\n';
} else {
cout << -1 << '\n';
}
}
return 0;
}
```
:::
### 子題三
改成讀入每筆資料並依序修改和計算,時間複雜度$\mathcal O(MlogM*Q)$
::: spoiler Python 70% code
```python=
import sys
sys.setrecursionlimit(1 << 25)
input = sys.stdin.readline
class UnionFind:
def __init__(self, size):
self.parent = list(range(size))
def find(self, x):
while self.parent[x] != x:
self.parent[x] = self.parent[self.parent[x]]
x = self.parent[x]
return x
def union(self, x, y):
xr, yr = self.find(x), self.find(y)
if xr == yr:
return False
self.parent[yr] = xr
return True
def kruskal(N, edges):
edges.sort(key=lambda x: x[2])
uf = UnionFind(N)
total = 0
count = 0
for u, v, w in edges:
if uf.union(u, v):
total += w
count += 1
if count == N - 1:
break
return total
N, M, Q = map(int, input().split())
edge_dict = {}
for _ in range(M):
S, T, C = map(int, input().split())
edge_dict[(S, T)] = C
updates = [tuple(map(int, input().split())) for _ in range(Q)]
K = int(input())
edges = [(u, v, w) for (u, v), w in edge_dict.items()]
if kruskal(N, edges) > K:
print(0)
else:
for i, (Qs, Qt, Qc) in enumerate(updates):
key = (Qs, Qt)
if key in edge_dict:
edge_dict[key] += Qc
else:
edge_dict[key] = Qc
edges = [(u, v, w) for (u, v), w in edge_dict.items()]
if kruskal(N, edges) > K:
print(i + 1)
break
else:
print(-1)
```
:::
:::spoiler C++ 70% code
```cpp=
#include <bits/stdc++.h>
using namespace std;
class UnionFind {
public:
vector<int> parent;
UnionFind(int size) {
parent.resize(size);
iota(parent.begin(), parent.end(), 0);
}
int find(int x) {
while (parent[x] != x) {
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}
bool union_sets(int x, int y) {
int xr = find(x), yr = find(y);
if (xr == yr) return false;
parent[yr] = xr;
return true;
}
};
int kruskal(int N, vector<tuple<int, int, int>>& edges) {
UnionFind uf(N);
sort(edges.begin(), edges.end());
int mst_cost = 0, count = 0;
for (auto& [w, u, v] : edges) {
if (uf.union_sets(u, v)) {
mst_cost += w;
count++;
if (count == N - 1) break;
}
}
return mst_cost;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int N, M, Q;
cin >> N >> M >> Q;
map<pair<int, int>, int> edge_dict;
for (int i = 0; i < M; i++) {
int S, T, C;
cin >> S >> T >> C;
edge_dict[{S, T}] = C;
}
vector<tuple<int, int, int>> updates(Q);
for (int i = 0; i < Q; i++) {
cin >> get<0>(updates[i]) >> get<1>(updates[i]) >> get<2>(updates[i]);
}
int K;
cin >> K;
vector<tuple<int, int, int>> edges;
for (auto& e : edge_dict) {
edges.push_back({e.second, e.first.first, e.first.second});
}
if (kruskal(N, edges) > K) {
cout << 0 << '\n';
return 0;
}
for (int i = 0; i < Q; i++) {
int Qs = get<0>(updates[i]), Qt = get<1>(updates[i]), Qc = get<2>(updates[i]);
if (edge_dict.count({Qs, Qt})) {
edge_dict[{Qs, Qt}] += Qc;
} else {
edge_dict[{Qs, Qt}] = Qc;
}
edges.clear();
for (auto& e : edge_dict) {
edges.push_back({e.second, e.first.first, e.first.second});
}
if (kruskal(N, edges) > K) {
cout << i + 1 << '\n';
return 0;
}
}
cout << -1 << '\n';
return 0;
}
```
:::
##### AC
由於Q的範圍達到$2*10^5$,故二分搜答案範圍`(0~Q)`降低時間複雜度。時間複雜度$\mathcal O(MlogM*logQ)$
:::spoiler Python AC(2.1s, 39MB) code
```python=
import sys
sys.setrecursionlimit(1 << 25)
input = sys.stdin.readline
class UnionFind:
def __init__(self, size):
self.parent = list(range(size))
def find(self, x):
while self.parent[x] != x:
self.parent[x] = self.parent[self.parent[x]]
x = self.parent[x]
return x
def union(self, x, y):
xr, yr = self.find(x), self.find(y)
if xr == yr:
return False
self.parent[yr] = xr
return True
def kruskal(N, edge_dict):
edges = sorted([(u, v, w) for (u, v), w in edge_dict.items()], key=lambda x: x[2])
uf = UnionFind(N)
mst_cost = 0
count = 0
for u, v, w in edges:
if uf.union(u, v):
mst_cost += w
count += 1
if count == N - 1:
break
return mst_cost
# 主程式
N, M, Q = map(int, input().split())
edge_dict_base = {}
for _ in range(M):
S, T, C = map(int, input().split())
edge_dict_base[(S, T)] = C
updates = [tuple(map(int, input().split())) for _ in range(Q)]
K = int(input())
def check(mid):
edge_dict = edge_dict_base.copy()
for i in range(mid):
Qs, Qt, Qc = updates[i]
key = (Qs, Qt)
if key in edge_dict:
edge_dict[key] += Qc
else:
edge_dict[key] = Qc
return kruskal(N, edge_dict) > K
if kruskal(N, edge_dict_base) > K:
print(0)
else:
l, r = 1, Q
ans = -1
while l <= r:
mid = (l + r) // 2
if check(mid):
ans = mid
r = mid - 1
else:
l = mid + 1
print(ans)
```
:::
::: spoiler C++ AC(0.4s,6.1MB) code
```cpp=
#include <bits/stdc++.h>
using namespace std;
class UnionFind {
public:
vector<int> parent;
UnionFind(int size) {
parent.resize(size);
iota(parent.begin(), parent.end(), 0);
}
int find(int x) {
while (parent[x] != x) {
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}
bool union_sets(int x, int y) {
int xr = find(x), yr = find(y);
if (xr == yr) return false;
parent[yr] = xr;
return true;
}
};
int kruskal(int N, map<pair<int, int>, int>& edge_dict) {
vector<tuple<int, int, int>> edges;
for (auto& e : edge_dict) {
edges.push_back({e.second, e.first.first, e.first.second});
}
sort(edges.begin(), edges.end());
UnionFind uf(N);
int mst_cost = 0, count = 0;
for (auto& [w, u, v] : edges) {
if (uf.union_sets(u, v)) {
mst_cost += w;
count++;
if (count == N - 1) break;
}
}
return mst_cost;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int N, M, Q;
cin >> N >> M >> Q;
map<pair<int, int>, int> edge_dict_base;
for (int i = 0; i < M; i++) {
int S, T, C;
cin >> S >> T >> C;
edge_dict_base[{S, T}] = C;
}
vector<tuple<int, int, int>> updates(Q);
for (int i = 0; i < Q; i++) {
cin >> get<0>(updates[i]) >> get<1>(updates[i]) >> get<2>(updates[i]);
}
int K;
cin >> K;
auto check = [&](int mid) {
map<pair<int, int>, int> edge_dict = edge_dict_base;
for (int i = 0; i < mid; i++) {
int Qs = get<0>(updates[i]), Qt = get<1>(updates[i]), Qc = get<2>(updates[i]);
if (edge_dict.count({Qs, Qt})) {
edge_dict[{Qs, Qt}] += Qc;
} else {
edge_dict[{Qs, Qt}] = Qc;
}
}
return kruskal(N, edge_dict) > K;
};
if (kruskal(N, edge_dict_base) > K) {
cout << 0 << '\n';
return 0;
}
int l = 1, r = Q, ans = -1;
while (l <= r) {
int mid = (l + r) / 2;
if (check(mid)) {
ans = mid;
r = mid - 1;
} else {
l = mid + 1;
}
}
cout << ans << '\n';
return 0;
}
```
:::