会魔法的东东 最小生成树问题

题意:

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

Input
第1行:一个数n
第2行到第n+1行:数wi
第n+2行到第2n+1行:矩阵即pij矩阵

Output
东东最小消耗的MP值

Example
Input
4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0

Output
9


思路:

这道题的目的是为了给所有的农田都灌溉上水,我们可以将题目中的“黄河之水天上来”看作一个超级原点,将这个原点和所有的农田看作是图中一个个点,点与点之间的边的权值则为“在田上施展魔法,使得黄河之水天上来”所消耗的MP或是“两块田的渠上建立传送门”所消耗的MP,于是这道题便转化成了一道求图的最小生成树的题目。

采用 kruskal 算法求解最小生成树。kruskal算法的思想是,将图中所有的边按照权重排序,依次取出最小的边,边能够加入到最小生成树中的条件是这条边不会与已经选好的边构成环。

我们可以采用结构体来存储边,结构体里的数据变量是两个顶点和边的权重,将所有的边存到一个边数组里,然后将数组排序,从前往后边的权重逐渐增加。如何判断“这条边不会与已经选好的边构成环”条件呢,若是构成环,实际上这条边的两个顶点已经在选好的边中出现,因此我们可以采用并查集的思想,若是这条边能够选入,则将两个顶点各自属于的集合合并,若是不能则意味着两个顶点隶属于一个集合。
(关于并查集的注意点请看之前的blog:https://blog.csdn.net/FoxMakarov/article/details/105169515)

注意:若是边权重为0,则代表这两个点之间没有边.


总结

要注意观察题意,如将“黄河之水天上来”作为一个超级原点,将消耗MP作为两点之间边的权值。

求最小生成树可采用kruskal算法。


代码:

#include<iostream>
#include<stdio.h>
#include<vector>
#include<algorithm>
using namespace std;
int n;
long long ans = 0;

struct Edge
{
	int u;
	int v;
	int w;

	Edge(int _u = 0,int _v = 0,int _w = 0):u(_u),v(_v),w(_w){}
	Edge(const Edge&t):u(t.u),v(t.v),w(t.w){}

	bool operator<(const Edge& t)const
	{
		return w < t.w;
	}
};

int par[100000] = { 0 };
int Rank[100000] = { 0 };

int find(int i)
{
	if (i == par[i])
		return i;
	else return par[i] = find(par[i]);
}

bool unite(int i, int j)
{
	int par1 = find(i);
	int par2 = find(j);
	if (par1 == par2)return false;//****************************已经在一个并查集里了
	if (Rank[par1] < Rank[par2])
	{
		Rank[par2] += Rank[par1];
		par[par1] = par2;
	}
	else
	{
		Rank[par1] += Rank[par2];
		par[par2] = par1;
	}
	return true;
}

Edge edge[100000];
long long number = 0;


int main()
{
	scanf_s("%d", &n);
	for (int i = 0; i <= n; i++)
	{
		par[i] = i;
	    Rank[i] = 1;
	}
	
	for (int i = 1; i < n+1; i++)
	{
		int water;
		scanf_s("%d", &water);
		edge[number++] = Edge(0, i, water);
	}

	for(int i = 1;i<=n;i++)
		for (int j = 1; j <= n; j++)
		{
			int pay;
			scanf_s("%d", &pay);
			if (pay == 0)continue;
			edge[number++] = Edge(i, j, pay);
		}
	sort(edge, &edge[number]);
	for (int i = 0; i < number; i++)
	{
		int u = edge[i].u;
		int v = edge[i].v;
		int water = edge[i].w;
		if (unite(u, v))
			ans += water;
	}
	printf("%lld", ans);


}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值