BZOJ2654 tree 生成树+二分法
题意:给定一张无向图,图中每条边均为白色或黑色,求有K个白色节点的边权和最小的生成树,数据保证有解
题解:
比较直观的想法是跑一个最小生成树,然后不断换边使得白边的数量等于K
然而我们可以给所有白边加上一个值,使其被选的几率增大或减小,而赋的值可以通过二分来找到。
#include <cstdio> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> using namespace std; const int MAXM=100000+2; struct EDGE{ int u,v,w,c; }e[MAXM]; int N,M,K,ans,tot,u[MAXM],v[MAXM],w[MAXM],c[MAXM],f[MAXM]; bool cmp(EDGE a,EDGE b){ return a.w==b.w?a.c<b.c:a.w<b.w;} int Find(int x){ return x==f[x]?x:f[x]=Find(f[x]);} bool Kruskal(int x){ int cnt=0;tot=0; for(int i=1;i<=N;i++)f[i]=i; for(int i=1;i<=M;i++){ e[i].u=u[i],e[i].v=v[i],e[i].w=w[i];e[i].c=c[i]; if(!c[i]) e[i].w+=x; } sort(e+1,e+M+1,cmp); for(int i=1;i<=M;i++){ int p=Find(e[i].u),q=Find(e[i].v); if(p!=q){ f[p]=q,tot+=e[i].w; if(!e[i].c) cnt++; } } return cnt>=K; } int main(){ scanf("%d %d %d",&N,&M,&K); for(int i=1;i<=M;i++){ scanf("%d %d %d %d",u+i,v+i,w+i,c+i); u[i]++;v[i]++; } int l=-105,r=105; while(l<=r){ int m=(l+r)>>1; if(Kruskal(m)) l=m+1,ans=tot-K*m; else r=m-1; } printf("%d",ans); return 0; }