题解 P2916 【[USACO08NOV]Cheering up the Cow G】

[USACO08NOV]Cheering up the Cow G

核心算法:Kruskal

题目中说:“删去尽可能多的边”。就是指保留一棵最小生成树。


构图

我们取一小段来分析一下:

(可以把这个理解为单位元

题解 P2916 【[USACO08NOV]Cheering up the Cow G】

按照题目中的意思,走过这一张的花费为:

C[1] + L + C[2] + L + C[1]

即:C[1] + C[1] + 2L + C[2]

题解 P2916 【[USACO08NOV]Cheering up the Cow G】

走过这一张的花费为:

C[1] + L(1,2) + C[2] + L(2,3) + C[3] + L(2,3) + C[2] + L(1,2) + C[1]

即:C[1] + (C[1] + 2L(1,2) + C[2]) + (C[2] + 2L(2,3) + C[3])

所以,我们可以推出:新的边的权值,为这条边 起点权值+终点权值+两倍边权

而最后的结果,还要加上一开始起点权值。 (即取点权中的最小值,因为题目中要求最小)

代码如下(细节见注释):

#include <bits/stdc++.h>
#define MAXN 1000000
#define INF 0x3f3f3f3f
int fat[MAXN],siz[MAXN];
int c[MAXN],n,m,ans=0;
struct EDGE{int from,to,val;}	e[MAXN];
//邻接矩阵 
bool cmp(EDGE x,EDGE y){return x.val<y.val;}
//比较器  按照边的权值排序 
int Find(int x)	{return (fat[x]==x) ? x :fat[x]=Find(fat[x]);}
void unionn (int x,int y)
{
	x=Find(x); y=Find(y);
	if(siz[x]>siz[y]) std::swap(x,y);
	fat[x]=y;	siz[y]+=siz[x];
}
//并查集模板 
bool kruskal()
{
	int k=0;
	std::sort(e+1,e+m+1,cmp);
	for(int i=1;i<=m;++i)
	{
		if(k==n-1)	break;
		if(Find(e[i].to)!=Find(e[i].from))
		{
			unionn(e[i].to,e[i].from);
			++k;	ans+=e[i].val;
		}
	}
	return (k==n-1);
}
// kruskal模板 
int main()
{
	int minn=INF;
	std::scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)	{fat[i]=i;	siz[i]=1;}
	//并查集初始化  
	for(int i=1;i<=n;++i)
	{
		std::scanf("%d",&c[i]);
		minn=std::min(minn,c[i]);
		//点权值中取最小值 
	}
	for(int i=1;i<=m;++i)
	{
		std::scanf("%d%d%d",&e[i].from,&e[i].to,&e[i].val);
		e[i].val=e[i].val*2+c[e[i].from]+c[e[i].to];
		//重新定义权值 
	}
	if(kruskal()) std::printf("%d",ans+minn);
	return 0;
}