第六周作业-C

题目描述:
东东在老家农村无聊,想种田。农田有 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值