1. 程式人生 > >【BZOJ2654】tree 二分+最小生成樹

【BZOJ2654】tree 二分+最小生成樹

con 連通圖 顏色 kruskal bool cpp || esp 答案

【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;
}

【BZOJ2654】tree 二分+最小生成樹