【BZOJ2654】tree 二分+最小生成树
【BZOJ2654】tree
Description
给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有need条白色边的生成树。
题目保证有解。
Input
第一行V,E,need分别表示点数,边数和需要的白色边数。
接下来E行,每行s,t,c,col表示这边的端点(点从0开始标号),边权,颜色(0白色1黑色)。
Output
一行表示所求生成树的边权和。
V<=50000,E<=100000,所有数据边权为[1,100]中的正整数。
Sample Input
2 2 1
0 1 1 1
0 1 2 0
0 1 1 1
0 1 2 0
Sample Output
2
题解:又是一种奇奇怪怪的做法~
如果我们给所有白色边增加边权,那么所选的白色边一定越来越少(反之同理)。所以我们二分给白色边增加多少边权,跑kruskal,最后再将增加的边权减去即可。
但是你可能怀疑二分的正确性?即如果给白色边边权加上mid,则所选白色边>need,如果加上mid+1,则所选白色边<need。解决方法是,在排序的时候,我们将白色边放在相同长度的黑色边之前。这样,因为mid+1时白边<mid,所以一定有若干=mid的黑边。在mid时,我们多选的白边就可以被黑边替换掉。所以在最后统计答案的时候,只需要ans-=mid*need即可。
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; int n,m,nd,ans,sum,cnt,wt; struct edge { int a,b,col,val; }p[100010]; int f[50010]; int rd() { int ret=0,f=1; char gc=getchar(); while(gc<'0'||gc>'9') {if(gc=='-')f=-f; gc=getchar();} while(gc>='0'&&gc<='9') ret=ret*10+gc-'0',gc=getchar(); return ret*f; } bool cmp(edge a,edge b) { return (a.val==b.val)?(a.col<b.col):(a.val<b.val); } int find(int x) { return (f[x]==x)?x:(f[x]=find(f[x])); } int solve(int x) { int i,ra,rb,ret; for(i=1;i<=m;i++) if(!p[i].col) p[i].val+=x; sort(p+1,p+m+1,cmp); sum=cnt=wt=0; for(i=1;i<=n;i++) f[i]=i; for(i=1;i<=m;i++) { ra=find(p[i].a),rb=find(p[i].b); if(ra!=rb) { cnt++,wt+=1-p[i].col,f[ra]=rb,sum+=p[i].val; if(cnt==n-1) { if(wt>=nd) ans=sum-x*nd,ret=1; else ret=0; } } } for(i=1;i<=m;i++) if(!p[i].col) p[i].val-=x; return ret; } int main() { int i,l=0,r=0,mid; n=rd(),m=rd(),nd=rd(); for(i=1;i<=m;i++) p[i].a=rd()+1,p[i].b=rd()+1,p[i].val=rd(),p[i].col=rd(),r=max(r,p[i].val+1); l=-r; while(l<r) { mid=l+r>>1; if(solve(mid)) l=mid+1; else r=mid; } printf("%d",ans); return 0; }
| 欢迎来原网站坐坐! >原文链接<