BZOJ2654 tree (wqs二分)
题目描述
给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有need条白色边的生成树。
题目保证有解。
一个最小生成树问题,但是我们要选need条白边,我们用g(i)表示选取i条白边的最优方案(生成树的权值最小),那么可以大致猜出g(i)是关于i的一个下凸函数,可以发现斜率k是有单调性的,我们二分这个斜率k,相当于给每条白边的权值加上一个k,统计数量use,如果use>=need,说明k小了,要增大,同理,use<need,要减小k。
那么问题来了,如果说当前白边加上mid后,白边条数use>need了,如果加上mid+1后,use<need了要怎么办?
题目中说到了:保证有解,所以出现上述情况时一定有黑边==白边的边权
所以我们只需要把一条黑边换成白边就好,即我们排序时如果黑边权值等于白边,则白边优先。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define N 50001 4 #define M 100001 5 int n,m,k; 6 struct node{ 7 int u,v,w,c; 8 }e[M]; 9 int use,sum,fa[N]; 10 bool cmp(node a,node b){ 11 if(a.w!=b.w) return a.w<b.w; 12 return a.c<b.c;//黑边白边权值一样,优先选白边 13 } 14 int find(int x){ 15 return fa[x]==x?x:fa[x]=find(fa[x]); 16 } 17 void check(int x){ 18 for(int i=1;i<=m;i++) 19 if(e[i].c==0) e[i].w+=x;//白边加权值 20 sort(e+1,e+m+1,cmp); 21 for(int i=1;i<=n;i++) fa[i]=i; 22 int now=0,fu,fv,j=1; 23 use=0,sum=0; 24 while(now!=n-1){ 25 fu=find(e[j].u);fv=find(e[j].v); 26 if(fu!=fv){ 27 now++; 28 fa[fu]=fv; 29 if(e[j].c==0) ++use;//统计使用的白边数量 30 sum+=e[j].w; 31 } 32 ++j; 33 } 34 for(int i=1;i<=m;i++) 35 if(e[i].c==0) e[i].w-=x;//还原 36 } 37 38 int main() 39 { 40 scanf("%d%d%d",&n,&m,&k); 41 for(int i=1;i<=m;i++){ 42 scanf("%d%d%d%d",&e[i].u,&e[i].v,&e[i].w,&e[i].c); 43 ++e[i].u;++e[i].v;//题目是编号从0开始,要+1 44 } 45 int l=-101,r=101,mid,ans;//值域[1,100]; 46 while(l<=r){ 47 mid=l+r>>1;check(mid); 48 if(use>=k){ 49 ans=sum-k*mid; 50 l=mid+1; 51 } 52 else r=mid-1; 53 } 54 cout<<ans; 55 return 0; 56 }