1. 程式人生 > 實用技巧 >題解 P2916 【[USACO08NOV]Cheering up the Cow G】

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

核心演算法:Kruskal

題目中說:“刪去儘可能多的邊”。就是指保留一棵最小生成樹。


構圖

我們取一小段來分析一下:

(可以把這個理解為單位元

按照題目中的意思,走過這一張的花費為:

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

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

走過這一張的花費為:

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