【Week6作业 C】掌握魔法の东东I【kruskal最小生成树】

题意:

东东在老家农村无聊,想种田。农田有 n 块,编号从 1~n。种田要灌氵。
众所周知东东是一个魔法师,他可以消耗一定的 MP 在一块田上施展魔法,使得黄河之水天上来。他也可以消耗一定的 MP 在两块田的渠上建立传送门,使得这块田引用那块有水的田的水。 (1<=n<=3e2)。
黄河之水天上来的消耗是 Wi,i 是农田编号 (1<=Wi<=1e5)。
建立传送门的消耗是 Pij,i、j 是农田编号 (1<= Pij <=1e5, Pij = Pji, Pii =0)。
求东东为所有的田灌氵的最小消耗。


思路:

考虑图的重构:加一个超级源点0号,并向n个点连边为Wi,然后对这n+1个点求最小生成树,所选的边的权值和即为最小消耗。
使用kruskal来求最小生成树。求最短边用到了小根堆,STL中的小根堆要有自定义的排序结构cmp,且应重载’( )’。判断要加入的边是否组成环路用到了并查集,注意这里并查集有0号点。因为题目一定满足有最小生成树,于是直接取了n条边,用ans来记录权值和。


总结:

一道最小生成树的题目。能想到重构图加上0号源点很重要。STL的小根堆应熟练使用,不要再从网上找。


代码:

#include <iostream>
#include <queue>
using namespace std;

int n;
int ans;	//最小消耗
int e[301][301];	//0号顶点为超级源点
//并查集
int par[301],rnk[301];
void init(int n)
{
	for(int i=0; i<=n; i++)
		par[i]=i,rnk[i]=1;
}
int find(int x)
{
	return par[x]==x?x:par[x]=find(par[x]);
}
bool unite(int x,int y)
{
	x=find(x),y=find(y);
	if(x==y)	return false;
	if(rnk[x]>rnk[y])
		par[y]=x,rnk[x]=(rnk[y]+=rnk[x]);
	else
		par[x]=y,rnk[y]=(rnk[x]+=rnk[y]);
	return true;
}
//边
struct edge
{
	int v,w;
	int z;
	edge(int v1=0,int w1=0,int z1=0)
	{
		v=v1,w=w1,z=z1;
	}
};
struct cmp
{
	bool operator () (edge a,edge b)
	{
		return a.z>b.z;	//小根堆用大于号 
	}
};
priority_queue<edge,vector<edge>,cmp> pq;	//小根堆
void kruskal()
{
	ans=0;
	//向小根堆中添加所有的边,应避免重复加入
	for(int i=0; i<=n; i++)
		for(int j=i+1; j<=n; j++)
		{
			edge theEdge(i,j,e[i][j]);
			pq.push(theEdge);
		}
	//并查集初始化
	init(n);
	for(int i=0; i<n; i++)	//共取n条边
	{
		edge theEdge=pq.top();
		pq.pop();
		int a=theEdge.v,b=theEdge.w;
		if(find(a)!=find(b))	//不构成环路
		{
			ans=ans+theEdge.z;
			unite(a,b);
		}
		else i--;
	}
	cout<<ans<<endl;
}
int main()
{
	cin>>n;
	e[0][0]=0;
	for(int i=1; i<=n; i++)
	{
		int w;
		cin>>w;
		e[i][0]=w,e[0][i]=w;
	}
	for(int i=1; i<=n; i++)
		for(int j=1; j<=n; j++)
			cin>>e[i][j];
	kruskal();

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值