题目描述:
东东在老家农村无聊,想种田。农田有 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值
sample:
Input
4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0
Output
9
题目分析:
本题如果没有“黄河之水天上来”那就是一个完美的最小生成树问题,但是这里可以用外来的力量灌水,所以我们把这个外来的力量看做一个点,这个点与所有的点都联通,恰好题目里给的数据大小正好。
for (i = 0;i < n; i++)
{
int w;
cin>>w;
addedge(0,i+1,w);
}
这里最小生成树就是找所有最小的边,只要不封闭就取出,这样我们就需要一个排序,当然可以operate重载,也可以利用sort自构函数解决
struct edge
{
int u;
int v;
int w;
}e[200000];
bool cmp(edge a,edge b)
{
if(a.w<b.w)
return true;
else
return false;
}
本题为了方便,我们用链表表示图。刚好还有个问题,就是如何判断是不是闭环,我们就用孩子双亲表示法来表示树,加入一个边的时候看看他两个点的双亲是不是一样,一样的话再加入这条边就会出来一个三角形,是封闭的,所以不能加入,反之就加入,这样有利用到了并查集,把加入的点合并到一个集合里面去
int find(int x)
{
if (father[x] == x)
return x;
else
return father[x] = find(father[x]);
}
void unite(int x, int y)
{
x = find(x);
y = find(y);
if (x > y)
swap(x, y);
if (x != y)
father[y] = x;
}
if (find(e[j].u) != find(e[j].v))
{
unite(e[j].u , e[j].v);
num++;
sum = sum + e[j].w;
}
当然本题还有一个小陷阱,就是它的输入是数组储存矩阵,但是这个题是链表储存矩阵,而且这个是无向图,也就是矩阵是对称的,我们只需要一条边,那么怎么办?输入的洪流是不可抗拒的,所以我们就先保存好,然后只需要取上半部分或者下半部分就可以了(我本人忽视了这里然后一直卡在test5不过,WA了十几次,(┬_┬))
for (int j = 1; j <= n; j++)
{
for (int k = 1; k <= n; k++)
{
cin >> m[j][k];
}
}
for (int j = 1; j <= n; j++)
{
for (int k = j; k <= n; k++)
{
if (j != k)
{
addedge(j,k,m[j][k]);
i++;
}
}
}
代码如下:
#include<iostream>
#include<algorithm>
using namespace std;
struct edge
{
int u;
int v;
int w;
}e[200000];
int m[400][400];//临时储存输入的矩阵表示的图
int father[400];//记录父亲
int i;//记录图的点
bool cmp(edge a,edge b)//比较
{
if(a.w<b.w)
return true;
else
return false;
}
void addedge(int u,int v,int w)//插入边
{
e[i].u=u;
e[i].v=v;
e[i].w=w;
}
int find(int x)//查找双亲
{
if (father[x] == x)
return x;
else
return father[x] = find(father[x]);
}
void unite(int x, int y)//结合
{
x = find(x);
y = find(y);
if (x > y)
swap(x, y);
if (x != y)
father[y] = x;
}
int main()
{
int n;
cin >> n;
for (int g = 0; g <= n; g++)//初始化
{
father[g] = g;
}
for (i = 0;i < n; i++)//对于“黄河之水天上来”的处理
{
int w;
cin>>w;
addedge(0,i+1,w);
}
for (int j = 1; j <= n; j++)//输入矩阵表示
{
for (int k = 1; k <= n; k++)
{
cin >> m[j][k];
}
}
for (int j = 1; j <= n; j++)//取一半插入边
{
for (int k = j; k <= n; k++)
{
if (j != k)
{
addedge(j,k,m[j][k]);
i++;
}
}
}
sort(e, e + i , cmp);//排序
int num = 0;
int sum = 0;
for (int j = 0; j < i; j++)
{
if (find(e[j].u) != find(e[j].v))//双亲不一样
{
unite(e[j].u , e[j].v);
num++;//当然要看取的点的数目,不能多于已知个数
sum = sum + e[j].w;
}
if (num == n)
{
break;
}
}
cout << sum;
}