C - 掌握魔法の东东 I
题目描述
东东在老家农村无聊,想种田。农田有 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值
Sample Input
4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0
Sample Output
9
题解
将每一块田看作一个点,两块田之间建立传送门相当于两个点之间有通路,消耗的mp值即为路径的权重。若只是把所有的田连在一起,则就是在n个点的图中找最小连通树的问题。
而黄河之水天上来的操作,则可以看作有一个源点0,有的点可以和0点相连。最后需要将所有的田和天上(n+1个点)连起来。
不管是水从天上来还是从其他田里来,都需要消耗MP值,把MP值看作路径的权。
使用kruskal算法,过程中使用了树形并查集的方法。
par数组存储的是属性并查集中一块田的父节点(顺着父节点往上找就能找到代表元)。total数组存储的是以i为代表元的一组田中边的权重之和(连通这些田需要的MP值)。
代码
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <vector>
using namespace std;
struct edge{
int u,v,w;
bool operator<(const edge&e)const{
return w>e.w;
}
};
vector<edge> v;
int n;//有多少块田
int total[400];
int par[400];
void init(){//初始化
for(int i=0;i<=n;i++){par[i]=i;total[i]=0;}
}
int find(int x){
return (par[x]==x)?x:(par[x]=find(par[x]));
}
void unite(int x,int y,int w){
x=find(x),y=find(y);
if(x==y){return;}
par[x]=y;
total[y]=total[y]+total[x]+w;
}
int main(int argc, char** argv) {
scanf("%d",&n);
for(int i=1;i<=n;i++){
int w;
scanf("%d",&w);
v.push_back({0,i,w});
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
int w;
scanf("%d",&w);
if(j>i){//一开始是等于,是不必要的操作
v.push_back({j,i,w});
}
}
}
make_heap(v.begin(),v.end());
init();//一开始忘了初始化
while(!v.empty()){
edge e=v[0];//由0改为1
pop_heap(v.begin(), v.end());
v.pop_back();
unite(e.u,e.v,e.w);
}
printf("%d\n",total[find(0)]);
return 0;
}