[BZOJ2654] tree (kruskal & 二分答案)
Description
给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有need条白色边的生成树。
题目保证有解。
Input
第一行V,E,need分别表示点数,边数和需要的白色边数。
接下来E行
每行s,t,c,col表示这边的端点(点从0开始标号),边权,颜色(0白色1黑色)。
Output
一行表示所求生成树的边权和。
Sample Input
2 2 1
0 1 1 1
0 1 2 0
0 1 1 1
0 1 2 0
Sample Output
2
HINT
数据规模和约定
0:V<=10
1,2,3:V<=15
0,..,19:V<=50000,E<=100000
所有数据边权为[1,100]中的正整数。
Source
Solution
如果给所有白色边加上一个权值,所形成的最小生成树的白边数会随该权值的增大而减小,满足单调性,可以二分权值。
排序有一个技巧:我们每次考虑最多可以使用多少条白边,所以排序时若权值相同,白边排前黑边排后。如果排序不管黑边白边,那么所求的白边数会小于期望的答案,可能会导致WA。
当然黑边在前白边在后也行,题是死的人是活的。
1 #include <bits/stdc++.h> 2 using namespace std; 3 struct edge 4 { 5 int u, v, w, col; 6 bool operator < (const edge &rhs) const 7 { 8 return w == rhs.w ? col < rhs.col : w < rhs.w; 9 } 10 }e[100005]; 11 int n, m, fa[50005], ans; 12 13 int getfa(int x) 14 { 15 return fa[x] = x == fa[x] ? x : getfa(fa[x]); 16 } 17 18 int Kruskal(int x) 19 { 20 int ecnt = 0, wcnt = 0, u, v; 21 ans = 0; 22 for(int i = 1; i <= n; i++) 23 fa[i] = i; 24 for(int i = 1; i <= m; i++) 25 if(!e[i].col) e[i].w += x; 26 sort(e + 1, e + m + 1); 27 for(int i = 1; i <= m; i++) 28 { 29 u = getfa(e[i].u), v = getfa(e[i].v); 30 if(u != v) 31 { 32 fa[v] = u, ans += e[i].w; 33 if(!e[i].col) wcnt++; 34 if(++ecnt >= n) break; 35 } 36 } 37 for(int i = 1; i <= m; i++) 38 if(!e[i].col) e[i].w -= x; 39 return wcnt; 40 } 41 42 int main() 43 { 44 int k, l = -105, r = 105, mid; 45 cin >> n >> m >> k; 46 for(int i = 1; i <= m; i++) 47 cin >> e[i].u >> e[i].v >> e[i].w >> e[i].col; 48 for(int i = 1; i <= m; i++) 49 e[i].u++, e[i].v++; 50 while(l < r - 1) 51 { 52 mid = (l + r) / 2; 53 if(Kruskal(mid) < k) r = mid; 54 else l = mid; 55 } 56 Kruskal(l); 57 cout << ans - l * k << endl; 58 return 0; 59 }