题意:
东东在家想种田,但要给每块田灌水,已知东东有魔法,可以在没块田上直接引水,需要消耗Wi的法力值。同时可以在两块填之间引水,需要Pij的法力值。易知必须至少从天上引一次水,才有初始水源。求灌满所有的田所需要的最小的法力值。
input:
第1行:一个数n ,1<=n<=3e2, 为农田的数量。
第2行到第n+1行:数wi,即为从每块田上直接引水所花费的法力值。
第n+2行到第2n+1行:矩阵即Pij矩阵,即在田ij之间引水所花费的法力值。
output:
东东最小消耗的MP值
样例输出输出:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200329115307814.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2JhbnppeGlhbmc=,size_16,color_FFFFFF,t_70)
思路:
即将天上的也作为一个原始起点,其与每个田之间都有一跳路径Mi,然后每块田之间也有路径Pij,让每块田中都有水,即每块田都要与原始起点有一条可以连通的路径即可,并且是最小路径,即在这N+1的点中求出最小生成树即可,最小生成树可以在每个点之间都建立联系,并且是最小的。利用克鲁斯卡尔算法,先用一个结构体数组存好每个边的出点、入点和边权。再将该数组按照边权的进行排序,从小到大。然后从数组中依次取出一个边,如果这个边的两个端点不在一个集合中,则将这两个点的集合合并,即使用并查集的思想。(一开始用的0 1标记来模拟并查集,但是在实现中的时候发现很多问题。)然后将n+1个点合并成一个树,即需要n次合并。之后便可得到最小生成树。输出答案。
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
struct edge{
int from,to,value;
bool operator<(const edge &p)const{
return value<p.value;
}
};
int fa[100100]; //点的标记
edge P[100100]; //边
int N; //农田的数量
int tot;
void addedge(int f, int t, int v){
P[tot].from=f;
P[tot].to=t;
P[tot].value=v;
tot++;
}
int findfa(int x){
while(x!=fa[x]){
x=fa[x];
}
return x;
}
void unit(int x,int y){
int fx=findfa(x);
int t=fa[y]; //t是y的头
while(y!=t){
fa[y]=x;
y=t;
t=fa[y];
}
fa[t]=x;
}
int main(){
cin>>N;
for(int i=0; i<=N;i++){
fa[i]=i;
}
tot=0;
int a;
for(int i=1; i<=N;i++){
scanf("%d",&a);
addedge(0,i,a);
}
for(int i=1; i<=N; i++){
for(int j=1; j<=N;j++){
scanf("%d",&a);
if(i!=j){
addedge(i,j,a);
}
}
}
sort(P,P+tot);
int i=0; //合并次数
int j=0; //已经用的边的数量
int sum=0;
while(i!=N){
if(findfa(P[j].from)!=findfa(P[j].to)){
unit(P[j].from,P[j].to);
i++;
sum+=P[j].value;
}
j++;
}
cout<<sum<<endl;
return 0;
}