题目:
东东在老家农村无聊,想种田。农田有 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值
样例:
Input
4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0
Output
9
这一题很明显是最小生成树的问题,但是引黄河之水这个条件,让我们要考虑有些点可能直接引水的消耗比建立传送门的消耗要小,而且至少要有一次引水。
为了解决这个问题,我们可以加入一个点,这个点与其余的点连接即代表着引黄河水到那块田。这样用kruskal算法可以轻松解决问题了。
kruskal函数:
void kruskal()
{
long long ans=0;
sort(e,e+(n+1)*n/2,comp);
int count=0;
for(int i=0;i<(n+1)*n/2;i++)
{
if(count==n)//当count=n的时候,说明所有的点都被连接起来了
{
break;
}
int a=find(e[i].start);//并查集操作
int b=find(e[i].end);
if(a!=b)//a!=b说明边e[i]的两个端点不在同一个集合里,说明选择边e[i]不会形成环
{
count++;
ans=ans+e[i].w;
unite(e[i].start,e[i].end);//说明边的两个端点已经属于同一集合了
}
}
cout<<ans<<'\n';
}
并查集操作:
int find(int x)//并查集的查找
{
if(pre[x]==x)
return x;
else
return find(pre[x]);
}
void unite(int x,int y)//并查集的合并
{
int a=find(x);
int b=find(y);
if(a!=b)
{
pre[a]=b;
}
}
以下是完整代码:
#include<iostream>
#include<algorithm>
using namespace std;
int p[400][400];
int n;
struct edge{
int start,end;
int w;
};
edge e[900000];
int cnt=0;//用于把矩阵转变为边集
int pre[400];//用于并查集
bool comp(edge e1,edge e2)
{
if(e1.w<e2.w)
return true;
else
return false;
}
int find(int x)//并查集的查找
{
if(pre[x]==x)
return x;
else
return find(pre[x]);
}
void unite(int x,int y)//并查集的合并
{
int a=find(x);
int b=find(y);
if(a!=b)
{
pre[a]=b;
}
}
void kruskal()
{
long long ans=0;
sort(e,e+(n+1)*n/2,comp);
int count=0;
for(int i=0;i<(n+1)*n/2;i++)
{
if(count==n)//当count=n的时候,说明所有的点都被连接起来了
{
break;
}
int a=find(e[i].start);//并查集操作
int b=find(e[i].end);
if(a!=b)//a!=b说明边e[i]的两个端点不在同一个集合里,说明选择边e[i]不会形成环
{
count++;
ans=ans+e[i].w;
unite(e[i].start,e[i].end);//说明边的两个端点已经属于同一集合了
}
}
cout<<ans<<'\n';
}
int main()
{
cin>>n;
for(int i=0;i<=n;i++)//初始化并查集要用到的数组
{
pre[i]=i;
}
p[0][0]=0;
for(int i=1;i<=n;i++)//在邻接矩阵里加入额外的0号点
{
int w;
cin>>w;
p[0][i]=p[i][0]=w;
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
cin>>p[i][j];
}
}
for(int i=0;i<=n;i++)//以主对角线为界,把邻接矩阵左上的边全部转换为结构体边edge
{
for(int j=i+1;j<=n;j++)
{
e[cnt].start=i;
e[cnt].end=j;
e[cnt].w=p[i][j];
cnt++;
}
}
kruskal();
}