题意:
东东在老家农村无聊,想种田。农田有 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);
}