【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

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;
}
posted @ 2017-07-16 10:08  CQzhangyu  阅读(1088)  评论(0编辑  收藏