问题 D: tree
题目描述
给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有need条白色边的生成树。
题目保证有解。
输入
第一行V,E,need分别表示点数,边数和需要的白色边数。
接下来E行,每行s,t,c,col表示这边的端点(点从0开始标号),边权,颜色(0白色1黑色)。
输出
一行表示所求生成树的边权和。
V<=50000,E<=100000,所有数据边权为[1,100]中的正整数。
样例输入
2 2 1
0 1 1 1
0 1 2 0
样例输出
2
完全没意识到要二分答案。。。
二分一个权值x,给白边边权全部加上x,根据kruskal的过程,部分白边会被卡掉;当白边数大于need时,提高下界;
反之降低上界。
注意:边权值和不能在二分答案中由(白边边权-x)+黑边边权来统计,因为会出现这种情况:二分出的mid所选出的白边数>need,mid+1所选出的白边数<need,这时可以在排序时优先按权值排序,权值相同时按颜色排序,当出现如上情况时,把白边当做黑边看,所以ans=sum-need*x。
代码:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define maxn 50005
#define maxx 100005
using namespace std;
inline int read()
{ char c=getchar();int x=0,y=1;
while(c<'0'||c>'9'){if(c=='-') y=-1;c=getchar();}
while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x*y;
}
int n,m,nd,num,nnum,fa[maxn],ans,sum,lef,rig,all;
inline int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
inline void unionn(int x,int y){fa[x]=y;}
struct road{int be,en,w,col;}ro[maxx],nro[maxx];
bool cmp(road x,road y){return x.w==y.w?x.col<y.col:x.w<y.w;}
void add(int x,int y,int z,int v){ro[++num].be=x;ro[num].col=v;ro[num].en=y;ro[num].w=z;}
void addv(int x,int y,int z,int v){nro[++nnum].be=x;nro[nnum].col=v;nro[nnum].en=y;nro[nnum].w=z;}
bool check(int x)
{ int k=0;all=0;sum=0;nnum=0;
for(int i=1;i<=n;++i) fa[i]=i;
for(int i=1;i<=num;++i)
if(!ro[i].col) addv(ro[i].be,ro[i].en,ro[i].w+x,ro[i].col);
else addv(ro[i].be,ro[i].en,ro[i].w,ro[i].col);
sort(nro+1,nro+nnum+1,cmp);
for(int i=1;i<=nnum;++i)
{ int a=find(nro[i].be),b=find(nro[i].en);
if(a!=b)
{ unionn(a,b);
++k;sum+=nro[i].w;
if(!nro[i].col) ++all;
}
if(k==n-1) break;
}
if(all>=nd) return 1;
return 0;
}
int main()
{ //freopen("nt2012_tree.in","r",stdin);
//freopen("nt2012_tree.out","w",stdout);
n=read();m=read();nd=read();
int x,y,z,v;
for(int i=1;i<=m;++i)
{ x=read()+1;y=read()+1;z=read();v=read();
add(x,y,z,v);
}
lef=-105;rig=105;
while(lef<=rig)
{ int mid=lef+rig>>1;
if(check(mid)) ans=sum-nd*mid,lef=mid+1;
else rig=mid-1;
}
printf("%d",ans);
return 0;
}