c++最小生成树

源代码

基础

由于个人习惯于Kruskal所以这里不介绍Prim
不过两个算法都一样

Kruskal算法

  1. 以边的权值大(小)排序
  2. 用并查集(只要其中一条边的两个点不在同一集合中便和并)

原理

  1. 将边按照从小到大的方式排序
  2. 如果边的两端不连通则选择这条边

并查集写法

在这里插入图片描述

int getf(int x)//获取祖先
{
	if (f[x] < 0)return x;
	f[x] = getf(f[x]);//路径压缩
	return f[x];
}
bool hb(int x,int y)
{
	x=getf(x),y=getf(y);
	if(x==y)//在同一个集合不能加入边
		return 0;
	if(f[x]<f[y])swap(x,y);//保证少的向多的和并(加不加速度影响不大)
	f[y]+=f[x];f[x]=y;//y数量增加  x的父亲指向y
	return 1;//可以和并
}

Kruskal

例如给出一个无向图要求最短生成树

输入
n,m//n个点m条边
x,y,k//x到y又一条边为z
如图
在这里插入图片描述

6 10
1 2 4
1 4 3
1 3 9
2 4 4
2 6 1
3 4 5
3 5 8
4 6 6
4 5 6
5 6 7

输出

19

代码

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#pragma warning(disable : 4996)
struct xy
{
	int x, y, z;
	void get()
	{
		scanf("%d%d%d", &x, &y, &z);
	}
//  友元函数     为<比较函数            为了sort方便
	friend bool operator < (xy a, xy b)
	{
		return a.z < b.z;
	}
};
xy a[1000000];
int f[1000000];
int getf(int x)//获取祖先
{
	if (f[x] < 0)return x;
	f[x] = getf(f[x]);//路径压缩
	return f[x];
}
bool hb(int x, int y)
{
	x = getf(x), y = getf(y);
	if (x == y)//在同一个集合不能加入边
		return 0;
	if (f[x] < f[y])swap(x, y);//保证少的向多的和并(加不加速度影响不大)
	f[y] += f[x]; f[x] = y;//y数量增加  x的父亲指向y
	return 1;//可以和并
}
int main()
{
	memset(f, -1, sizeof f);//一定要注意 千万不要落下
	int n, m;
	scanf("%d%d", &n, &m);
	for (int i = 0; i < m; i++)
		a[i].get();
	sort(a, a + m);
	int sum = 0;
	int yqd = 0;//已确定的边数
	for (int i = 0; i < m, yqd != n - 1; i++)
	{
		if (hb(a[i].x, a[i].y))
		{
			sum += a[i].z;
			yqd++;
		}
	}
	printf("%d\n", sum);
	return 0;
}

题目做法

北极通讯网络

题目描述
原题链接
北极的某区域共有 n 座村庄,每座村庄的坐标用一对整数 (x,y) 表示。为了加强联系,决定在村庄之间建立通讯网络。通讯工具可以是无线电收发机,也可以是卫星设备。所有的村庄都可以拥有一部无线电收发机, 且所有的无线电收发机型号相同。但卫星设备数量有限,只能给一部分村庄配备卫星设备。
在这里插入图片描述
不同型号的无线电收发机有一个不同的参数 d,两座村庄之间的距离如果不超过 d 就可以用该型号的无线电收发机直接通讯,d 值越大的型号价格越贵。拥有卫星设备的两座村庄无论相距多远都可以直接通讯。

现在有 k 台卫星设备,请你编一个程序,计算出应该如何分配这 k 台卫星设备,才能使所拥有的无线电收发机的 d 值最小,并保证每两座村庄之间都可以直接或间接地通讯。

例如,对于下面三座村庄:

其中 |AB|=10,|BC|=20,|AC|=105–√≈22.36
如果没有任何卫星设备或只有 1 台卫星设备 (k=0 或 k=1),则满足条件的最小的 d=20,因为 A 和 B,B 和 C 可以用无线电直接通讯;而 A 和 C 可以用 B 中转实现间接通讯 (即消息从 A 传到 B,再从 B 传到 C);

如果有 2 台卫星设备 (k=2),则可以把这两台设备分别分配给 B 和 C ,这样最小的 d 可取 10,因为 A 和 B 之间可以用无线电直接通讯;B 和 C 之间可以用卫星直接通讯;A 和 C 可以用 B 中转实现间接通讯。

如果有 3 台卫星设备,则 A,B,C 两两之间都可以直接用卫星通讯,最小的 d 可取 0。

【输入】
第一行为由空格隔开的两个整数 n,k;

第 2∼n+1 行,每行两个整数,第 i 行的 xi,yi​ 表示第 i 座村庄的坐标 (xi,yi)。

【输出】
一个实数,表示最小的 d 值,结果保留 2 位小数。

【输入样例】

3 2
10 10
10 0
30 0

【输出样例】

10.00

【提示】
数据范围:
对于全部数据,1≤n≤500,0≤x,y≤104,0≤k≤100。
思路
这道题非常简单只要把卫星设备当作在Kruskal中需要找到的边减小1
当k=1或者k=0时特判无效
对于每一个村庄只要求出意两个村庄的路线边可以
解法

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
#pragma warning(disable : 4996)
struct xy
{
	int x, y;
	double z;
//  友元函数     为<比较函数            为了sort方便
	friend bool operator < (xy a, xy b)
	{
		return a.z < b.z;
	}
};
xy a[1000000];
int f[1000], top = 0;
double x[1000];
double y[1000];
int getf(int x)//获取祖先
{
	if (f[x] < 0)return x;
	f[x] = getf(f[x]);//路径压缩
	return f[x];
}
bool hb(int x, int y)
{
	x = getf(x), y = getf(y);
	if (x == y)//在同一个集合不能加入边
		return 0;
	if (f[x] < f[y])swap(x, y);//保证少的向多的和并(加不加速度影响不大)
	f[y] += f[x]; f[x] = y;//y数量增加  x的父亲指向y
	return 1;//可以和并
}
int main()
{
	memset(f, -1, sizeof f);//一定要注意 千万不要落下
	int n, k;
	scanf("%d%d", &n, &k);
	for (int i = 0; i < n; i++)
		scanf("%lf%lf", &x[i], &y[i]);
	for (int i = 0; i < n; i++)
	{
		for (int j = i + 1; j < n; j++)
		{
			a[top].x = i;
			a[top].y = j;
			a[top++].z = sqrt((x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]));
		}
	}
	if (k == 0)
		k = 1;
	double sum = 0;
	int yqd = 0;//已确定的边数
	sort(a, a + top);
	for (int i = 0; i < top, yqd < n - k; i++)
	{
		if (hb(a[i].x, a[i].y))
		{
			sum = a[i].z;//仔细读题求最大值,及最后一个链接的边
			yqd++;
		}
	}
	printf("%.2lf\n", sum + 1e-6);//加上 le-6 是因为避免浮点误差
	return 0;
}

新的开始

原题链接
【题目描述】
发展采矿业当然首先得有矿井,小 FF 花了上次探险获得的千分之一的财富请人在岛上挖了 n 口矿井,但他似乎忘记考虑的矿井供电问题……

为了保证电力的供应,小 FF 想到了两种办法:

在这一口矿井上建立一个发电站,费用为 v(发电站的输出功率可以供给任意多个矿井)。

将这口矿井与另外的已经有电力供应的矿井之间建立电网,费用为 p。

小 FF 希望身为「NewBe_One」计划首席工程师的你帮他想出一个保证所有矿井电力供应的最小花费。

【输入】
第一行一个整数 n,表示矿井总数。

第 2∼n+1 行,每行一个整数,第 i 个数 vi 表示在第 i 口矿井上建立发电站的费用。

接下来为一个 n×n 的矩阵 p,其中 pi,j 表示在第 i 口矿井和第 j 口矿井之间建立电网的费用(数据保证有pi,j=pj,i​ ,且 pi,i=0。)

【输出】
输出仅一个整数,表示让所有矿井获得充足电能的最小花费。

【输入样例】

4  
5  
4 
4  
3  
0 2 2 2  
2 0 3 3  
2 3 0 4  
2 3 4 0

【输出样例】

9

【提示】
样例解释

小 FF 可以选择在 4 号矿井建立发电站然后把所有矿井都不其建立电网,总花费是 3+2+2+2=9。

数据范围:

对于 30% 的数据:1≤n≤50;

对于 100% 的数据:1≤n≤300,0≤vi,pi,j≤105​​ 。
思路
我们可以新建虚拟0号节点将0号到(1~n)连路值为v[i]
之后做最小生成树就好
解法

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
#pragma warning(disable : 4996)
//====边的结构题定义====//
struct xy
{
	int x, y, z;
	//  友元函数     为<比较函数            为了sort方便
	friend bool operator < (xy a, xy b)
	{
		return a.z < b.z;
	}
};
//=====全局变量定义=====//
xy a[1000000];
int f[310], top = 0;
int v[310], p[310][310];
//=======寻找祖先=======// 
int getf(int x)//获取祖先
{
	if (f[x] < 0)return x;
	f[x] = getf(f[x]);//路径压缩
	return f[x];
}
//=========合并=========//
bool hb(int x, int y)
{
	x = getf(x), y = getf(y);
	if (x == y)//在同一个集合不能加入边
		return 0;
	if (f[x] < f[y])swap(x, y);//保证少的向多的和并(加不加速度影响不大)
	f[y] += f[x]; f[x] = y;//y数量增加  x的父亲指向y
	return 1;//可以和并
}
int main()
{
	//======变量初始化======//
	memset(f, -1, sizeof f);//一定要注意 千万不要落下
	int n;
	//=========输入=========//
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
		scanf("%d", &v[i]);
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
			scanf("%d", &p[i][j]);
	//=======转化成边=======//
	for (int i = 1; i <= n; i++)
		a[top].x = 0, a[top].y = i, a[top].z = v[i], top++;
	for (int i = 1; i <= n; i++)
		for (int j = i + 1; j <= n; j++)
			a[top].x = i, a[top].y = j, a[top].z = p[i][j], top++;
	//=========计算=========//
	sort(a, a + top);
	int yqd = 0, sum = 0;
	for (int i = 0; i < top && yqd < n; i++)
		if (hb(a[i].x, a[i].y))
			yqd++, sum += a[i].z;
	//=========输出=========//
	printf("%d\n", sum);
	return 0;
}

构造完全图

原题链接
【题目描述】
对于完全图 G,若有且仅有一棵最小生成树为 T,则称完全图 G 是树 T 扩展出的。

给你一棵树 T,找出 T 能扩展出的边权和最小的完全图 G。

【输入】
第一行 N 表示树 T 的点数;

接下来 N−1 行三个整数 Si,Ti,Di​​​​ ;描述一条边(Si,Ti)权值为 Di ;

保证输入数据构成一棵树。

【输出】
输出仅一个数,表示最小的完全图 G 的边权和。

【输入样例】

4  
1 2 1  
1 3 1  
1 4 2

【输出样例】

12

【提示】
样例说明

添加 D(2,3)=2,D(3,4)=3,D(2,4)=3 即可。

数据范围:

对于 20% 的数据,N≤10;

对于 50% 的数据,N≤1000;

对于 100% 的数据,N≤105,1≤Di≤105​ 。
【思路】
这道题一看,好像和最小生成树没有关系,其实不然
仔细分析Kruskal的算法步骤我们能发现
在这里插入图片描述
所以只要在进行Kruskal时稍加改动即可
注意要使用long long

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
#pragma warning(disable : 4996)
//====边的结构题定义====//
struct xy
{
	int x, y, z;
	friend bool operator < (xy a, xy b)
	{
		return a.z < b.z;
	}
};
//=====全局变量定义=====//
xy a[1000000];
long long f[110000], top = 0;
//=======寻找祖先=======// 
int getf(int x)
{
	if (f[x] < 0)return x;
	f[x] = getf(f[x]);
	return f[x];
}
//=========合并=========//
bool hb(int x, int y)
{
	x = getf(x), y = getf(y);
	if (x == y)
		return 0;
	if (f[x] < f[y])swap(x, y);
	f[y] += f[x]; f[x] = y;
	return 1;
}
int main()
{
	//======变量初始化======//
	memset(f, -1, sizeof f);
	int n;
	//=========输入=========//
	scanf("%d", &n);
	for (int i = 1; i < n; i++)
	{
		scanf("%d%d%d", &a[top].x, &a[top].y, &a[top].z);
		top++;
	}
	//=========计算=========//
	sort(a, a + top);
	long long sum = 0;
	for (int i = 0; i < top; i++)
	{
		sum += f[getf(a[i].x)] * f[getf(a[i].y)] * (a[i].z + 1) - 1;
		hb(a[i].x, a[i].y);
	}
	//=========输出=========//
	printf("%lld\n", sum);
	return 0;
}

待完成

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值