题目
题意
东东在老家农村无聊,想种田。农田有 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
Example Output
9
题目大意
本题给出图中的n个节点,同时给出两种操作:1、在两个节点间连接一条边,使得两个节点都有水,边权已给出;2、消耗 Wi 为第 i 个节点供水,题目要求用以上两种操作,使得消耗最小且每个节点都有水。实质上可以转化为最小生成树问题。
解题思路
本题明显是一道最小生成树问题,但是不好处理每个节点上的“黄河之水天上来”问题。此时只需要添加一个 0 号节点即可,Wi 可以理解为是 0 号节点与 i 号节点之间的边权。之后再使用Kruskal算法找到最短路径长度即可。
此处的Kruskal算法借助并查集实现。首先将所有边按权值由小到大排序,可以借助小根堆实现。随后每次取边,若这条边的头尾节点不在一个并查集内,就可以选用这条边,否则不选。由于本题添加了节点 0 ,所以当选定边数为 n 条是就可以结束循环了,求出选定边权总和就是最小消耗值。
本题代码可以用于存储Kruskal算法模板,效果较好。
具体代码
#include <iostream>
#include <queue>
#define MAXN 100005
using namespace std;
struct road{
int from;
int to;
int length;
bool operator<(const road& a) const
{
return length > a.length; //小根堆,反号大根堆
}
};
priority_queue<road> a; //自定数据类型优先队列
int fa[305];
int getfa(int n)
{
if(fa[n] != n)
{
fa[n] = getfa(fa[n]);
}
return fa[n];
}
void unity(int x, int y)
{
int f1 = getfa(x);
int f2 = getfa(y);
fa[f1] = f2;
}
int main(int argc, char** argv)
{
for(int i = 0; i < 305; i++)
{
fa[i] = i;
}
int n,cnt = 0,ans = 0;
road temp;
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> temp.length;
temp.from = 0;
temp.to = i;
a.push(temp);
}
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= n; j++)
{
cin >> temp.length;
temp.from = i;
temp.to = j;
if(i < j)
{
a.push(temp);
}
}
}
while(cnt < n)
{
temp = a.top();
a.pop();
if(getfa(temp.from) != getfa(temp.to))
{
ans += temp.length;
unity(temp.from,temp.to);
cnt++;
}
}
cout << ans;
return 0;
}```