[bzoj2654]tree_二分_kruskal
tree bzoj-2654
题目大意:给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有need条白色边的生成树。题目保证有解。
注释:$1\le V\le 5\cdot 10^4$,$1\le E \le 10^5$,$1\le val_i\le 100$。
想法:乍一看最小生成树,然后直接gg,没有更好的处理方法。用什么样的方法可以将白边的边数恰好为need且边权和最小?EdwardFrog讲课的时候就提出了这样的方法:二分出往白边上加上多少权值。首先,统一的往白边上加权值会使得加权后的最小生成树中白边个数下降,这是单调的,我们可以用二分实现。二分的同时求出加权后的使得最小生成树中白边恰好有need条的边权val,求出最小生成树后将val*need减掉即可。
最后,附上丑陋的代码... ...
#include <stdio.h> #include <string.h> #include <algorithm> #include <stdlib.h> using namespace std; struct E { int a,b,v,c,d; }e[100050]; bool cmp(const E &x,const E &y) { if(x.c==y.c) return x.d<y.d; return x.c<y.c; } int n,m,k,fa[50050],reimu; int find(int x) { return fa[x]==x?x:fa[x]=find(fa[x]); } void init(int x) { for(int i=0;i<=n;i++) fa[i]=i; for(int i=1;i<=m;i++) { if(e[i].d==0) { e[i].c=e[i].v+x; } else e[i].c=e[i].v; } sort(e+1,e+m+1,cmp); } int check(int mn) { reimu=0; int re=0,nowe=0; init(mn); for(int i=1;i<=m;i++) { int x=e[i].a,y=e[i].b; int dx=find(x),dy=find(y); //printf("%d %d",x,y); if(dx!=dy) { reimu+=e[i].c; fa[dx]=dy; if(!e[i].d) re++; nowe++; if(nowe==n-1) { break; } } } return re; } int main() { scanf("%d%d%d",&n,&m,&k); for(int i=1;i<=m;i++) { scanf("%d%d%d%d",&e[i].a,&e[i].b,&e[i].v,&e[i].d); } int l=-1000,r=1000,ans; while(l<r) { int mid=(l+r)>>1; int z=check(mid); if(z<k)r=mid; else l=mid+1,ans=reimu-k*mid; } printf("%d\n",ans); }
小结:二分显然是正确的。
| 欢迎来原网站坐坐! >原文链接<