[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);
}

 

    小结:二分显然是正确的。

posted @ 2018-05-11 11:29  JZYshuraK_彧  阅读(192)  评论(0编辑  收藏  举报