掌握魔法の东东
一、题目
东东在老家农村无聊,想种田。农田有 n 块,编号从 1~n。种田要灌氵
众所周知东东是一个魔法师,他可以消耗一定的 MP 在一块田上施展魔法,使得黄河之水天上来。他也可以消耗一定的 MP 在两块田的渠上建立传送门,使得这块田引用那块有水的田的水。 (1<=n<=3e2)黄河之水天上来的消耗是 Wi,i 是农田编号 (1<=Wi<=1e5),建立传送门的消耗是 Pij,i、j 是农田编号 (1<= Pij <=1e5, Pij = Pji, Pii =0)。
求东东为所有的田灌氵的最小消耗。
二、输入
第1行:一个数n
第2行到第n+1行:数wi
第n+2行到第2n+1行:矩阵即pij矩阵
三、输出
东东最小消耗的MP值
四、样例输入输出
Input
4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0
Output
9
五、解题思路
并查集思想+Kruskal算法。
两块农田之间建立传送门可以抽象乘加权无向边,而天上引水可以抽象成一个点,该点到其他点均有加权无向边。将这两种边存入自定义类型的边数组,并根据权值排序。逐个选择权值最小的边,将两个点所在的集合合并,若能合并,则二者原本不连通,可以添加该边,若不能合并,则说明二者原来就是连通的,添加该边后,构成环路,不符合规则,所以不添加。每添加一条边,则加上该边的权值,直到遍历完所有的边。
注意:输入建立传送门消耗的数组时,因为数组是对称的,所以存一半的边即可。所有的点均合并为一个集合后应该就可以结束循环,不用遍历完所有边,可以继续优化,不过样例代码未体现。
六、样例代码
#include<iostream>
#include<algorithm>
#include<stdio.h>
using namespace std;
int n,m;
int a[50005];
int ans=0;
struct edge
{
int u,v,w;
edge()
{
u=-1;v=-1;w=-1;
}
void in(int uu,int vv,long long int ww)
{
u=uu;v=vv;w=ww;
}
bool operator < (const edge& e)
{
return w<e.w;
}
};
edge e[100005];
int find(int x){
if(a[x] == x)
return x;
a[x] = find(a[x]);
return a[x];
}
bool unite(int x,int y)
{
x=find(x);
y=find(y);
if(x==y) return false;
a[x]=y;
return true;
}
int main()
{
cin>>n;
int w;
int k=0;//边数
for(int i=0;i<=n;i++) a[i] = i;
for(int i=1;i<=n;i++)
{
cin>>w;
e[k].in(0,i,w);
k++;
}
int p;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
cin>>p;
if(i>j)
{
e[k].in(i,j,p);
k++;
}
}
}
sort(e,e+k);
for(int i=0;i<k;i++)
{
if(unite(e[i].u,e[i].v))
{
ans=ans+e[i].w;
}
}
printf("%d\n",ans);
return 0;
}