题意:
东东在老家农村无聊,想种田。农田有 n 块,编号从 1~n。种田要灌氵。
众所周知东东是一个魔法师,他可以消耗一定的 MP 在一块田上施展魔法,使得黄河之水天上来。他也可以消耗一定的 MP 在两块田的渠上建立传送门,使得这块田引用那块有水的田的水。 (1<=n<=3e2)。
黄河之水天上来的消耗是 Wi,i 是农田编号 (1<=Wi<=1e5)。
建立传送门的消耗是 Pij,i、j 是农田编号 (1<= Pij <=1e5, Pij = Pji, Pii =0)。
求东东为所有的田灌氵的最小消耗。
思路:
考虑图的重构:加一个超级源点0号,并向n个点连边为Wi,然后对这n+1个点求最小生成树,所选的边的权值和即为最小消耗。
使用kruskal来求最小生成树。求最短边用到了小根堆,STL中的小根堆要有自定义的排序结构cmp,且应重载’( )’。判断要加入的边是否组成环路用到了并查集,注意这里并查集有0号点。因为题目一定满足有最小生成树,于是直接取了n条边,用ans来记录权值和。
总结:
一道最小生成树的题目。能想到重构图加上0号源点很重要。STL的小根堆应熟练使用,不要再从网上找。
代码:
#include <iostream>
#include <queue>
using namespace std;
int n;
int ans; //最小消耗
int e[301][301]; //0号顶点为超级源点
//并查集
int par[301],rnk[301];
void init(int n)
{
for(int i=0; i<=n; i++)
par[i]=i,rnk[i]=1;
}
int find(int x)
{
return par[x]==x?x:par[x]=find(par[x]);
}
bool unite(int x,int y)
{
x=find(x),y=find(y);
if(x==y) return false;
if(rnk[x]>rnk[y])
par[y]=x,rnk[x]=(rnk[y]+=rnk[x]);
else
par[x]=y,rnk[y]=(rnk[x]+=rnk[y]);
return true;
}
//边
struct edge
{
int v,w;
int z;
edge(int v1=0,int w1=0,int z1=0)
{
v=v1,w=w1,z=z1;
}
};
struct cmp
{
bool operator () (edge a,edge b)
{
return a.z>b.z; //小根堆用大于号
}
};
priority_queue<edge,vector<edge>,cmp> pq; //小根堆
void kruskal()
{
ans=0;
//向小根堆中添加所有的边,应避免重复加入
for(int i=0; i<=n; i++)
for(int j=i+1; j<=n; j++)
{
edge theEdge(i,j,e[i][j]);
pq.push(theEdge);
}
//并查集初始化
init(n);
for(int i=0; i<n; i++) //共取n条边
{
edge theEdge=pq.top();
pq.pop();
int a=theEdge.v,b=theEdge.w;
if(find(a)!=find(b)) //不构成环路
{
ans=ans+theEdge.z;
unite(a,b);
}
else i--;
}
cout<<ans<<endl;
}
int main()
{
cin>>n;
e[0][0]=0;
for(int i=1; i<=n; i++)
{
int w;
cin>>w;
e[i][0]=w,e[0][i]=w;
}
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
cin>>e[i][j];
kruskal();
}