【BZOJ2654】【最小生成树】题解 Tree (luogu p2619)

题目描述
给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有need条白色边的生成树。
https://www.luogu.org/problemnew/show/P2619
输入输出格式
输入格式:
第一行V,E,need分别表示点数,边数和需要的白色边数。
输出格式:
一行表示所求生成树的边权和。
说明
0:V<=10
1,2,3:V<=15
0,…,19:V<=50000,E<=100000
所有数据边权为[1,100]中的正整数。
接下来E行
每行s,t,c,col表示这边的端点(点从0开始标号),边权,颜色(0白色1黑色)。

首先,分析这道题,是一个关于最小生成树的问题,由于顶点数是50000,我们肯定不能用邻接矩阵,考虑邻接表。
然后,就很容易想到是要跑克鲁斯卡尔啦。

int find(int x) {return f[x]==x?x:f[x]=find(f[x]);}

void kruskal(){
	for(int i=1;cnt!=n-1;++i){
		int r1=find(edge[i].start),r2=find(edge[i].end);
		if(r1!=r2) {
			f[find(r1)]=find(r2),cnt++;
			if(edge[i].c==0) temp++;
			    ans+=edge[i].v;
		}
	}
}

一个裸的克鲁斯卡尔。。。。
接下来,针对next的值首先可以想到枚举,把每个白边的权值减少一定的值,看减少多少会使最小生成树的白边数正好为need
那么,优化一下,在白边权值不断减少的过程中,白边的数量是递增的,那么满足一个单调的要求,所以,这里可以用二分,二分的正确性也可以由此证明
每次枚举一个mid的值,给每个白边的权值都加上mid,如果这时白边数大于等于need,这时候对mid的值进行减小,反之增加,每次跑一遍克鲁斯卡尔,更新ans的值(ans的值要减去增加的mid的权值)
他的细节处理是非常多的,每次跑克鲁斯卡尔之前都要进行清零(考试时候少清一个调了几百年QAQ)(捂脸)
下面附上AC代码(吐槽一波考试数据的变态,十五个点连了二十万条边)

#include 
#include 
#include 
#define FOR(i,n,m) for(int i=n;i<=m;++i)  //一个喜欢用宏定义的懒人,不用在意
#define OPEN(n) freopen(n".in","r",stdin); freopen(n".out","w",stdout);
using namespace std;

const int N=102000;
int f[N],n,need,e,l,r,mid,temp,ans,_ans,cnt;
struct node {int start,end,c,v;}edge[N]; 
int find(int x) {return f[x]==x?x:f[x]=find(f[x]);}
bool cmp(node a,node b) {return a.v==b.v?a.c<b.c:a.v<b.v;} //排序,按顺序,如果相同,白边在前

void kruskal(){
	mid=(l+r)>>1;
	FOR(i,1,e) {if(edge[i].c==0) edge[i].v+=mid;}
	FOR(i,1,n+1) f[i]=i;
	ans=0,temp=0,cnt=0; 	//清零
	sort(edge+1,edge+e+1,cmp); 
	for(int i=1;cnt!=n-1;++i){
		int r1=find(edge[i].start),r2=find(edge[i].end);
		if(r1!=r2) {
			f[find(r1)]=find(r2),cnt++;
			if(edge[i].c==0) temp++;
			    ans+=edge[i].v;
		}
	}
}
//克鲁斯卡尔啊啊啊

void in(){
	scanf("%d%d%d",&n,&e,&need);
	FOR(i,1,e){
		scanf("%d%d%d%d",&edge[i].start,&edge[i].end,&edge[i].v,&edge[i].c);
		edge[i].end++;
		edge[i].start++;
	}
}

void work(){
	in();
	l=-105,r=105;
	while(l<=r){
		kruskal();
		if(temp>=need) l=mid+1,_ans=ans-need*mid;
		else r=mid-1;
		FOR(i,1,e) if(edge[i].c==0) edge[i].v-=mid;
	}
	printf("%d",_ans);
}

int main(){
	
	//OPEN("tree")
	//咳咳,考试的文件名
	
	work();
	
	return 0;
}

这是本蒟蒻的第一篇博客,各位大佬不要笑话

你可能感兴趣的:(题解)