题目
东东在老家农村无聊,想种田。农田有 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值
Example
input
4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0
output
9
解题思路
1.两个田之间的边有权,是pij
我们要选择一种消耗最小的办法,很明显是一道最小生成树的问题。
2.问题在于黄河之水天上来,所以我们采用一种办法,建立一个点,叫做源点,这点是用于黄河之水天上来的。意思就是,他和每一个点的边的权重,就是Wi。
3.这样,我们就可以生成最小生成树了。
代码实现
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
//采用邻接表 装点
//用并查集判断是否形成环路
//令0点为原点,黄河之水天上来
const int Max = 100000;
const int Max_n = 50000;
//图
struct edge{
int u,v,w;
bool operator<(const edge &e )
{
if(w!=e.w) return w<e.w;
else if(u!=e.u ) return u<e.u;
else return v<e.v;
}
};
edge e[Max];int ei; //装所有边的
void addE( int u,int v,int w )
{
edge E1;
E1.u = u,E1.v=v,E1.w=w;
e[ei] = E1;ei++;
}
//并查集
int par[Max_n]; //老大
int men[Max_n]; //这个组有多少人
int find(int x)
{
if(par[x] == x) return x;
else par[x] = find(par[x]);
return par[x];
}
bool unite(int x,int y)
{
x = find(x),y = find(y);
if( x==y ) return 0;
if( men[x] > men[y] ) par[y] = par[x],men[x]+=men[y];
else par[x] = par[y] , men[y]+=men[x];
return 1;
}
void ini(int n)
{
for(int i=0;i<=n;i++)
{par[i] = i;men[i] =1;}
}
//
int n;
int wi;
int main()
{
ios::sync_with_stdio(0);
cin>>n;
ini(n);
for(int i=1;i<=n;i++) //0号点设为天 ,黄河之水天上来相当于
//0号点到各个点的路径长度
{
cin>>wi;
addE(i,0,wi);
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
cin>>wi;
addE(i,j,wi);
}
}
sort(e,e+ei);
int sum=0;
int cn=0;
for(int i=0;i<ei;i++)
{
if(unite(e[i].u,e[i].v))
sum = sum + e[i].w;
}
cout<<sum<<endl;
return 0;
}
小结
采用克鲁斯卡尔算法,对边集进行的操作。
踩了一个坑,是关于sort的,我的operator< 只判断了w,结果导致我出错了。对于所有的u,v,w,虽然我们只判断w,但是w相等时,u,v该指定一种排序。