题意:
东东在老家农村无聊,想种田。农田有 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值
样例输入:
4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0
样例输出:
9
思路:
题目分析:
如果没有把“黄河之水天上来”删掉,那么就是一个最小生成树问题
那么怎么把它转化为最小生成树问题呢?加一个超级源点0 号,并向n 个点连边为Wi,然后对这 n+1 个点跑最小生成树即可
最小生成树:
这里我采用kruskal算法: 将全部边按照权值由小到大排序。 按顺序(边权由小到大的顺序)考虑每条边,只要这条边和我们已经选 择的边不构成圈,就保留这条边,否则放弃这条边。 成功选择(n-1)条边后,形成一棵最小生成树,如果算法无法选择出 (n-1)条边,则说明原图不连通。
总结:
1.能够想到加一个点跑最小生成树很重要!
2.最小生成树:kruskal,prim
代码:
#include<cstdio>
#include<list>
#include<algorithm>
using namespace std;
struct edge{
int from;
int to;
int weight;
edge(int a,int b,int c){from=a;to=b;weight=c;}
bool operator<(edge &e){
if(weight!=e.weight) return weight<e.weight;
}
};
int n,p,wi,sum;
list<edge> G;
int par[310],ran[310];
void init(int n){
for(int i=0;i<=n;i++){
par[i]=i; ran[i]=1;
}
}
int find(int x){
return par[x]==x?x:par[x]=find(par[x]);
}
bool unite(int x,int y){
x=find(x); y=find(y);
if(x==y) return false;
if(ran[x]>ran[y]) swap(x,y); //始终保证y为大树
par[x]=y; ran[y]=(ran[x]+=ran[y]); //这里注意两个rank都更新
}
void kruskal(){
init(n);
int num=0; //记录生成树中边的条数
while(num<n&&G.size()!=0){
int a=find(G.front().from);
int b=find(G.front().to);
if(a==b) G.pop_front(); //都在生成树中
else{
sum+=G.front().weight;
unite(G.front().from,G.front().to); //合并
G.pop_front(); num++;
}
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&wi);
G.push_back({0,i,wi});
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
scanf("%d",&p);
if(p!=0) G.push_back({i,j,p});
}
}
G.sort();
kruskal();
printf("%d\n",sum);
return 0;
}