WEEK6 周记 作业——最小生成树_掌握魔法的东东
一、题意
1.简述
东东在老家农村无聊,想种田。农田有 n 块,编号从 1~n。种田要灌氵
众所周知东东是一个魔法师,他可以消耗一定的 MP 在一块田上施展魔法,使得黄河之水天上来。他也可以消耗一定的 MP 在两块田的渠上建立传送门,使得这块田引用那块有水的田的水。 (1<=n<=3e2)
黄河之水天上来的消耗是 Wi,i 是农田编号 (1<=Wi<=1e5)
建立传送门的消耗是 Pij,i、j 是农田编号 (1<= Pij <=1e5, Pij = Pji, Pii =0)
东东为所有的田灌氵的最小消耗### 2.输入格式
输入文件包含多组测试数据。对于每组测试数据,第一行一个整数N (N<=10000),接下来有N-1行,每一行两个数,对于第i行的两个数,它们表示与i号电脑连接的电脑编号以及它们之间网线的长度。网线的总长度不会超过10^9,每个数之间用一个空格隔开。
2.输入格式
第1行:一个数n
第2行到第n+1行:数wi
第n+2行到第2n+1行:矩阵即pij矩阵
3.输出格式
东东最小消耗的MP值
4.样例
Input
4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0
Output
9
二、算法
主要思路
如果题意是从某一个田开始灌水,只要与这个田连通的就都有水,花费只是开辟两块田之间路线的花费之和,那么就是很典型的一个最小生成树问题。
但是这个题还给出了一个使某个田有水的方法:从天上引水。
有个很巧妙的思路:将“天”看作是一个源节点。这样就可以在多加了一个源节点的全连通图上进行最小生成树算法,很容易就获得结果。
不难理解:实际上每种灌水的方案都可以看作是这个多加一个源节点的图的一颗生成树,而这个图的生成树必然可以成为一种灌水的方案,所以这个图的最小生成树的花费就是最优方案的花费。
三、代码
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
int n;
int p[310][310];//设置0号节点代表天
struct triple{
int a,b,lth;
triple(int a,int b,int lth)
:a(a),b(b),lth(lth){}
bool operator<(const triple& t)const{//为什么必须写const
return lth>t.lth;
}
};
priority_queue<triple> q;
pair<int,int> f[310];//<pre_node,total_lth>
int find(int a){
if(f[a].first==a){
return a;
}
int root = find(f[a].first);
f[a].first = root;
return root;
}
int unit(int a,int b){
if(f[a].second<f[b].second)
swap(a,b);//保证a这棵树是节点最多的
f[b].first = a;
f[a].second += f[b].second;
return a;
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n+1;i++){
f[i].first = i;
f[i].second = 0;
}
for(int i=1;i<n+1;i++){
int w;
scanf("%d",&p[0][i]);
p[i][0] = p[0][i];
triple t(0,i,p[0][i]);
q.push(t);
}
for(int i=1;i<n+1;i++){
for(int j=1;j<n+1;j++){
scanf("%d",&p[i][j]);
if(i>=j) continue;
p[j][i] = p[i][j];
triple t(i,j,p[i][j]);
q.push(t);
}
}
int root;
while(!q.empty()){
triple min = q.top();
q.pop();
int root1 = find(min.a);
int root2 = find(min.b);
if(root1!=root2){
root = unit(root1,root2);
f[root].second += min.lth;
}
}
root = find(0);
printf("%d",f[root].second);
return 0;
}