[BZOJ 2654]tree(陈立杰)
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
V<=50000,E<=100000,所有数据边权为[1,100]中的正整数。
题解
二分+$kruskal$
如果直接$kruskal$求最小生成树,是无法保证白边数量的,那么我们考虑如果改变白边的数量。我们可以把白边全部都加上一个权值,也就是我们二分的值,然后跑最小生成树,同时记录白边数量。当白边数量>=$need$时,$l=mid+1$,否则$r=mid−1$,更新答案就是这棵生成树的权值和减去所有白边的增量。
证明:
我们发现,如果我们给白边增加权值,做最小生成树,由于白边权值增大,导致不容易选白边。记$f(x)$为给白边增加$x$($x$可为负)权值,做最小生成树后,选白边的数量。可以发现,$f(x)$随$x$增大而减小,显然可以二分。
其次,我们发现,由于黑边的权值是不变的,与白边权值不相互影响。同样由于白边之间关系相对不变,必然选出的$need$条白边一定是符合题意的。
1 #include<map> 2 #include<ctime> 3 #include<cmath> 4 #include<queue> 5 #include<stack> 6 #include<cstdio> 7 #include<string> 8 #include<vector> 9 #include<cstring> 10 #include<cstdlib> 11 #include<iostream> 12 #include<algorithm> 13 #define LL long long 14 #define RE register 15 #define IL inline 16 using namespace std; 17 const int V=50000; 18 const int E=100000; 19 20 int mid; 21 int v,e,need,ans,cnt,tmp; 22 struct tt 23 { 24 int u,v,c,col; 25 }edge[E+5]; 26 27 IL int Kruskal(); 28 bool comp(const tt &a,const tt &b) {return a.c+(a.col^1)*mid<b.c+(b.col^1)*mid;} 29 30 int set[V+5]; 31 IL int find(int r) {return set[r]!=-1 ? set[r]=find(set[r]):r;} 32 33 int main() 34 { 35 scanf("%d%d%d",&v,&e,&need); 36 for (RE int i=1;i<=e;i++) scanf("%d%d%d%d",&edge[i].u,&edge[i].v,&edge[i].c,&edge[i].col); 37 int l=-100,r=100; 38 while (l<=r) 39 { 40 mid=(l+r)>>1; 41 if (Kruskal()>=need) l=mid+1,ans=tmp; 42 else r=mid-1; 43 } 44 printf("%d\n",ans); 45 return 0; 46 } 47 48 IL int Kruskal() 49 { 50 tmp=cnt=0; 51 int k=0; 52 memset(set,-1,sizeof(set)); 53 sort(edge+1,edge+1+e,comp); 54 for (RE int i=1;i<=e;i++) 55 { 56 int q=find(edge[i].u); 57 int p=find(edge[i].v); 58 if (p!=q) 59 { 60 k+=edge[i].col^1; 61 set[q]=p; 62 cnt++; 63 tmp+=edge[i].c; 64 if (cnt==v-1) break; 65 } 66 } 67 return k; 68 }
感谢Hzoi_Maple!
由于$COGS$数据会有不满足恰好$need$条白边的情况
打个比方有这样的数据:加$0$时大于$need$,加$1$就小于$need$了。
这样应该在跑最小生成树的时候把所有的白边都加上加的那个权值,结果就是最小生成树的权值和减去$need*$加上的权值,多出来的那一部分完全可以当做黑边来看,因为数据是$100000$的,这样就可以了。(来自Hzoi_Maple)
排序的时候,如果边权相同,要把白边放在前面。
要计算当前至多能取多少白边,当然要把白边放前面。由于保证有解,在$cnt>=need$且$cnt$取最小值的方案下,一定能有黑边把多余的白边代替掉。
1 #include<map> 2 #include<ctime> 3 #include<cmath> 4 #include<queue> 5 #include<stack> 6 #include<cstdio> 7 #include<string> 8 #include<vector> 9 #include<cstring> 10 #include<cstdlib> 11 #include<iostream> 12 #include<algorithm> 13 #define LL long long 14 #define RE register 15 #define IL inline 16 using namespace std; 17 const int V=50000; 18 const int E=100000; 19 20 int mid; 21 int v,e,need,ans,cnt,tmp; 22 struct tt 23 { 24 int u,v,c,col,rc; 25 }edge[E+5]; 26 27 IL int Kruskal(); 28 IL void change(); 29 bool comp(const tt &a,const tt &b) {return a.rc==b.rc ? a.col<b.col:a.rc<b.rc;} 30 31 int set[V+5]; 32 IL int find(int r) {return set[r]!=-1 ? set[r]=find(set[r]):r;} 33 34 int main() 35 { 36 scanf("%d%d%d",&v,&e,&need); 37 for (RE int i=1;i<=e;i++) scanf("%d%d%d%d",&edge[i].u,&edge[i].v,&edge[i].c,&edge[i].col); 38 int l=-100,r=100; 39 while (l<=r) 40 { 41 mid=(l+r)>>1; 42 if (Kruskal()>=need) l=mid+1,ans=tmp-need*mid; 43 else r=mid-1; 44 } 45 printf("%d\n",ans); 46 return 0; 47 } 48 49 IL void change() 50 { 51 for (RE int i=1;i<=e;i++) edge[i].rc=edge[i].c+(edge[i].col^1)*mid; 52 } 53 IL int Kruskal() 54 { 55 change(); 56 tmp=cnt=0; 57 int k=0; 58 memset(set,-1,sizeof(set)); 59 sort(edge+1,edge+1+e,comp); 60 for (RE int i=1;i<=e;i++) 61 { 62 int q=find(edge[i].u); 63 int p=find(edge[i].v); 64 if (p!=q) 65 { 66 k+=edge[i].col^1; 67 set[q]=p; 68 cnt++; 69 tmp+=edge[i].rc; 70 if (cnt==v-1) break; 71 } 72 } 73 return k; 74 }
博主蒟蒻,随意转载。但必须附上原文链接:http://www.cnblogs.com/NaVi-Awson/,否则你会终生找不到妹子!!!