week6作业B题 掌握魔法の东东 I kruskal

题目:
东东在老家农村无聊,想种田。农田有 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();
}
发布了24 篇原创文章 · 获赞 5 · 访问量 267

猜你喜欢

转载自blog.csdn.net/qq_45639151/article/details/105185440