(Week 10)最小生成树(C++,prim,Kruskal,并查集)

news2024/11/27 10:33:33

文章目录

  • Einstein学画画(C++,欧拉路)
    • 题目描述
    • 输入格式
    • 输出格式
    • 样例 #1
      • 样例输入 #1
      • 样例输出 #1
    • 提示
    • 解题思路:
  • 并查集(C++)
    • [蓝桥杯 2017 国 C] 合根植物(C++,并查集)
    • 题目描述
    • 输入格式
    • 输出格式
    • 样例 #1
      • 样例输入 #1
      • 样例输出 #1
    • 提示
    • 样例解释
    • 解题思路:
  • 灾后重建(C++,Floyd)
    • 题目背景
    • 题目描述
    • 输入格式
    • 输出格式
    • 样例 #1
      • 样例输入 #1
      • 样例输出 #1
    • 提示
    • 解题思路:
  • [NOIP2017 提高组] 奶酪(C++,并查集)
    • 题目背景
    • 题目描述
    • 输入格式
    • 输出格式
    • 样例 #1
      • 样例输入 #1
      • 样例输出 #1
    • 提示
    • 解题思路:
  • [HAOI2006]聪明的猴子(C++,最小生成树)
    • 题目描述
    • 输入格式
    • 输出格式
    • 样例 #1
      • 样例输入 #1
      • 样例输出 #1
    • 提示
    • 解题思路:

Einstein学画画(C++,欧拉路)

题目描述

Einstein 学起了画画。

此人比较懒~~,他希望用最少的笔画画出一张画……

给定一个无向图,包含 n n n 个顶点(编号 1 ∼ n 1 \sim n 1n), m m m 条边,求最少用多少笔可以画出图中所有的边。

输入格式

第一行两个整数 n , m n, m n,m

接下来 m m m 行,每行两个数 a , b a, b a,b a ≠ b a \ne b a=b),表示 a , b a, b a,b 两点之间有一条边相连。

一条边不会被描述多次。

输出格式

一个数,即问题的答案。

样例 #1

样例输入 #1

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

样例输出 #1

1

提示

对于 50 % 50 \% 50% 的数据, n ≤ 50 n \le 50 n50 m ≤ 100 m \le 100 m100

对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 1000 1 \le n \le 1000 1n1000 1 ≤ m ≤ 10 5 1 \le m \le {10}^5 1m105

解题思路:

本文虽然不是转载博文…但是也差不多了,原文出处:洛谷博主TheAutumnGlory

题解传送门:题解 P1636 【Einstein学画画】 - ζั͡އއއ๓秋 的博客 - 洛谷博客 (luogu.com.cn)

了解题目大意后感觉这题有一笔画的意味,这就要提到欧拉路

先说明一个定义:顶点的度:指和该点相关联的边数

我们称顶点的度为奇数的点为奇点

然后说明欧拉路

1)存在欧拉路的条件:图是联通的,且存在两个奇点

2)如果存在两个奇点,欧拉路一定是从一个奇点出发,在另一个奇点结束

注意:无向图只会存在偶数个奇点

所以解题思路就很简单了,累计每一个点的顶点的度,然后统计奇点的个数ans

最后输出ans/2即可

当然,ans == 0的时候应该输出1,此时的图会变为欧拉图

AC代码如下

#include <iostream>
using namespace std;
const int max_n = 1000;
const int max_m = 1e5;

int ans = 0;//度数为奇数的点的个数
int nodes[max_n + 1] = { 0 };

int main() {
	int n, m, u, v;
	cin >> n >> m;
	for (int i = 0; i < m; i++) {//存图
		cin >> u >> v;
		nodes[u]++, nodes[v]++;//累计点的度数
	}

	for (int i = 1; i <= n; i++) {
		if (nodes[i] % 2 != 0) {//度数为奇数
			ans++;
		}
	}
	//答案
	if (ans == 0) cout.put('1');
	else cout << ans / 2 << endl;
	return 0;
}

并查集(C++)

根据下面这道题讲下并查集

(其实本来是写题解的…写着写着就变成算法说明了)

[蓝桥杯 2017 国 C] 合根植物(C++,并查集)

题目描述

w 星球的一个种植园,被分成 m × n m \times n m×n 个小格子(东西方向 m m m 行,南北方向 n n n 列)。每个格子里种了一株合根植物。

这种植物有个特点,它的根可能会沿着南北或东西方向伸展,从而与另一个格子的植物合成为一体。

如果我们告诉你哪些小格子间出现了连根现象,你能说出这个园中一共有多少株合根植物吗?

输入格式

第一行,两个整数 m m m n n n,用空格分开,表示格子的行数、列数( 1 < m , n < 1000 1<m,n<1000 1<m,n<1000)。

接下来一行,一个整数 k k k,表示下面还有 k k k 行数据 ( 0 < k < 1 0 5 ) (0<k<10^5) (0<k<105)

接下来 k k k 行,第行两个整数 a a a b b b,表示编号为 a a a 的小格子和编号为 b b b 的小格子合根了。

格子的编号一行一行,从上到下,从左到右编号。

比如: 5 × 4 5 \times 4 5×4 的小格子,编号:

1  2  3  4
5  6  7  8
9  10 11 12
13 14 15 16
17 18 19 20

输出格式

一行一个整数,表示答案

样例 #1

样例输入 #1

5 4
16
2 3
1 5
5 9
4 8
7 8
9 10
10 11
11 12
10 14
12 16
14 18
17 18
15 19
19 20
9 13
13 17

样例输出 #1

5

提示

样例解释

时限 1 秒, 256M。蓝桥杯 2017 年第八届国赛

解题思路:

emmm我也不知道该怎么引入,直接进行说明吧

并查集实现的就是合并两个元素(也可以是集合)成为一个集合

首先我们要知道如何辨别一个集合

我们知道一棵树所有子节点的根节点是相同的,换言之,根节点可以代表一棵树,抽象一点,就可以代表一个集合

那么如何寻访根节点呢?

int find(x) {//寻访节点x的根节点
	if (father[x] == x)//x是根节点
		return x;
	else//x不是根节点
		return find(father[x]);//寻访x的父节点
}

这个递归,应该…很好理解吧

但是如果一棵树的深度过深的话会导致寻访很慢,于是我们进行路径压缩

int find(x) {//寻访节点x的根节点
	if (father[x] == x)//x是根节点
		return x;
	else {//x不是根节点
		father[x] = find(father[x]);//路径压缩
		return father[x];//寻访x的父节点
	}
}

就是把所有子节点都连到根节点上

可以将以上代码优化

int find(x) {
	return x == father[x] ? x : (father[x] = find(father[x]));
}

学会了辨别一个集合,接下来学习如何合并两个元素(或集合)

void merge(int x, int y) {
	father[x] = father[y];
}

当然前提是判断两个元素不在一个集合中

bool is_same(int x, int y) {
	if (find(x) == find(y)) return true;//一个集合
	else return false;//两个集合
}

至此本题讲解完…好像没完,用递归寻访根节点会被卡掉

接下来介绍优化寻访的另一种方法,按秩合并

寻访慢的原因就是树的深度过深,我们希望深度尽可能的浅,这就是按秩合并的基本思想

void merge(int x, int y) {
	if (depths[x] <= depths[y]) {//如果x是更简单的树
		father[y] = father[x];//连接简单树到复杂树上
	}
	else father[x] = father[y];
	
	if (depths[x] == depths[y])
		depths[y]++;//如果深度相同,y的深度++,可以自行思考一下为什么(很简单的)
}

使用按秩合并记得要把所有元素的深度初始化为1

最后简单说明一下题意就可以明白了

其实二维的平面是假想的,直接维护一个m*n大小的数组就可以了

寻访根节点可以修改为while循环,大多数返回后没有指令的递归都可以优化为while不定循环

最后,AC代码如下

//并查集
#include <iostream>
using namespace std;
const int max_m = 1000;
const int max_n = 1000;

int ans;
int map[max_m * max_n] = { 0 };
int depths[max_m * max_n] = { 1 };

//寻访根节点
int find(int x) {
	while (map[x] != x)	x = map[x];
	return x;
}

//按秩合并
void merge(int x, int y) {
	if (depths[x] >= depths[y]) map[y] = map[x];
	else map[x] = map[y];

	if (depths[x] == depths[y]) depths[y]++;
}

int main() {
	int m, n, k, u, v;
	cin >> m >> n >> k;
	int mul = m * n;
	for (int i = 1; i <= mul; i++) {//初始化
		map[i] = i;
		depths[i] = 1;
	}
	ans = m * n;

	for (int i = 0; i < k; i++) {//合并集合
		cin >> u >> v;
		int temp_1 = find(u);
		int temp_2 = find(v);
		if (temp_1 != temp_2) ans--;//集合数目-1
		merge(temp_1, temp_2);
	}
	cout << ans;
	return 0;
}

灾后重建(C++,Floyd)

题目背景

B 地区在地震过后,所有村庄都造成了一定的损毁,而这场地震却没对公路造成什么影响。但是在村庄重建好之前,所有与未重建完成的村庄的公路均无法通车。换句话说,只有连接着两个重建完成的村庄的公路才能通车,只能到达重建完成的村庄。

题目描述

给出 B 地区的村庄数 N N N,村庄编号从 0 0 0 N − 1 N-1 N1,和所有 M M M 条公路的长度,公路是双向的。并给出第 i i i 个村庄重建完成的时间 t i t_i ti,你可以认为是同时开始重建并在第 t i t_i ti 天重建完成,并且在当天即可通车。若 t i t_i ti 0 0 0 则说明地震未对此地区造成损坏,一开始就可以通车。之后有 Q Q Q 个询问 ( x , y , t ) (x,y,t) (x,y,t),对于每个询问你要回答在第 t t t 天,从村庄 x x x 到村庄 y y y 的最短路径长度为多少。如果无法找到从 x x x 村庄到 y y y 村庄的路径,经过若干个已重建完成的村庄,或者村庄 x x x 或村庄 y y y 在第 t t t 天仍未重建完成,则需要返回 -1

输入格式

第一行包含两个正整数 N , M N,M N,M,表示了村庄的数目与公路的数量。

第二行包含 N N N个非负整数 t 0 , t 1 , … , t N − 1 t_0, t_1,…, t_{N-1} t0,t1,,tN1,表示了每个村庄重建完成的时间,数据保证了 t 0 ≤ t 1 ≤ … ≤ t N − 1 t_0 ≤ t_1 ≤ … ≤ t_{N-1} t0t1tN1

接下来 M M M行,每行 3 3 3个非负整数 i , j , w i, j, w i,j,w w w w为不超过 10000 10000 10000的正整数,表示了有一条连接村庄 i i i与村庄 j j j的道路,长度为 w w w,保证 i ≠ j i≠j i=j,且对于任意一对村庄只会存在一条道路。

接下来一行也就是 M + 3 M+3 M+3行包含一个正整数 Q Q Q,表示 Q Q Q个询问。

接下来 Q Q Q行,每行 3 3 3个非负整数 x , y , t x, y, t x,y,t,询问在第 t t t天,从村庄 x x x到村庄 y y y的最短路径长度为多少,数据保证了 t t t是不下降的。

输出格式

Q Q Q行,对每一个询问 ( x , y , t ) (x, y, t) (x,y,t)输出对应的答案,即在第 t t t天,从村庄 x x x到村庄 y y y的最短路径长度为多少。如果在第t天无法找到从 x x x村庄到 y y y村庄的路径,经过若干个已重建完成的村庄,或者村庄x或村庄 y y y在第 t t t天仍未修复完成,则输出 − 1 -1 1

样例 #1

样例输入 #1

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

样例输出 #1

-1
-1
5
4

提示

对于 30 % 30\% 30%的数据,有 N ≤ 50 N≤50 N50

对于 30 % 30\% 30%的数据,有 t i = 0 t_i= 0 ti=0,其中有 20 % 20\% 20%的数据有 t i = 0 t_i = 0 ti=0 N > 50 N>50 N>50

对于 50 % 50\% 50%的数据,有 Q ≤ 100 Q≤100 Q100

对于 100 % 100\% 100%的数据,有 N ≤ 200 N≤200 N200 M ≤ N × ( N − 1 ) / 2 M≤N \times (N-1)/2 MN×(N1)/2 Q ≤ 50000 Q≤50000 Q50000,所有输入数据涉及整数均不超过 100000 100000 100000

解题思路:

根据题目的一个奇怪的条件:对于 Q Q Q次询问,数据保证了 t t t是不下降的。

以及 Q Q Q的最大值是 50000 50000 50000

可以知道不采用动态规划很可能就被卡掉了

再看到随机的起点、要求最短路径,已经可以确定采用floyd算法了

只不过需要增加一个时间判断

思路大体不变,先初始化任意两个节点的距离为无穷大、到达自己的距离为0

然后单独开一个数组用于保存输入的时间

再进行存图(题中已经说明无重边)

最后说明本题的关键解题思路

先看一下原始的floyd算法

for (int i = 0; i < n; i++)//尝试把节点i加入路径
	for (int j = 0; j < n; j++)//更新最短路径
		for (int k = 0; k < n; k++)
			dist[j][k] = min(dist[j][k], dist[j][i] + dist[i][k]);

我们只需要保证尝试加入的节点i是修复完毕的即可

while (j < n) {
	if (times[j] > t) {//保证节点合法
		j--; break;
	}
	for (int k = 0; k < n - 1; k++)//尝试更新最短距离
		for (int l = k; l < n; l++)
			map[l][k] = map[k][l] = min(map[k][l], map[k][j] + map[j][l]);
	j++;
}

注意,即使节点n-1未修复完毕也要尝试更新最短路径,可以自行思考一下为什么

AC代码如下

//Floyd
#include <iostream>
#include <memory.h>
using namespace std;
const int max_n = 200;
const int NaN = 0x3F3F3F3F;

int map[max_n][max_n] = { 0 };//存图
int times[max_n] = { 0 };

int main() {
	memset(map, 0x3F, sizeof(int) * max_n * max_n);
	for (int i = 0; i < max_n; i++) map[i][i] = 0;//初始化

	int n, m, u, v, w, q, t;
	cin >> n >> m;
	for (int i = 0; i < n; i++) cin >> times[i];
	for (int i = 0; i < m; i++) {//存图
		cin >> u >> v >> w;
		map[u][v] = map[v][u] = w;//不含重边
	}
	
	cin >> q;
	int j = 0;//尝试加入序号为j的节点
	for (int i = 0; i < q; i++) {//询问
		cin >> u >> v >> t;
		while (j < n) {
			if (times[j] > t) {//保证节点合法
				j--; break;
			}
			for (int k = 0; k < n - 1; k++)//尝试更新最短距离
				for (int l = k; l < n; l++)
					map[l][k] = map[k][l] = min(map[k][l], map[k][j] + map[j][l]);
			j++;
		}
		
		if (j < u || j < v || map[u][v] == NaN) cout << -1 << endl;
		else cout << map[u][v] << endl;
		j++;
	}
	return 0;
}

这里注意一下要在输出answer之后将j++,否则会导致重复加入同一个节点,造成TLE

[NOIP2017 提高组] 奶酪(C++,并查集)

题目背景

NOIP2017 提高组 D2T1

题目描述

现有一块大奶酪,它的高度为 h h h,它的长度和宽度我们可以认为是无限大的,奶酪中间有许多半径相同的球形空洞。我们可以在这块奶酪中建立空间坐标系,在坐标系中,奶酪的下表面为 z = 0 z = 0 z=0,奶酪的上表面为 z = h z = h z=h

现在,奶酪的下表面有一只小老鼠 Jerry,它知道奶酪中所有空洞的球心所在的坐标。如果两个空洞相切或是相交,则 Jerry 可以从其中一个空洞跑到另一个空洞,特别地,如果一个空洞与下表面相切或是相交,Jerry 则可以从奶酪下表面跑进空洞;如果一个空洞与上表面相切或是相交,Jerry 则可以从空洞跑到奶酪上表面。

位于奶酪下表面的 Jerry 想知道,在不破坏奶酪的情况下,能否利用已有的空洞跑 到奶酪的上表面去?

空间内两点 P 1 ( x 1 , y 1 , z 1 ) P_1(x_1,y_1,z_1) P1(x1,y1,z1) P 2 ( x 2 , y 2 , z 2 ) P2(x_2,y_2,z_2) P2(x2,y2,z2) 的距离公式如下:

d i s t ( P 1 , P 2 ) = ( x 1 − x 2 ) 2 + ( y 1 − y 2 ) 2 + ( z 1 − z 2 ) 2 \mathrm{dist}(P_1,P_2)=\sqrt{(x_1-x_2)^2+(y_1-y_2)^2+(z_1-z_2)^2} dist(P1,P2)=(x1x2)2+(y1y2)2+(z1z2)2

输入格式

每个输入文件包含多组数据。

第一行,包含一个正整数 T T T,代表该输入文件中所含的数据组数。

接下来是 T T T 组数据,每组数据的格式如下: 第一行包含三个正整数 n , h , r n,h,r n,h,r,两个数之间以一个空格分开,分别代表奶酪中空洞的数量,奶酪的高度和空洞的半径。

接下来的 n n n 行,每行包含三个整数 x , y , z x,y,z x,y,z,两个数之间以一个空格分开,表示空洞球心坐标为 ( x , y , z ) (x,y,z) (x,y,z)

输出格式

T T T 行,分别对应 T T T 组数据的答案,如果在第 i i i 组数据中,Jerry 能从下表面跑到上表面,则输出 Yes,如果不能,则输出 No

样例 #1

样例输入 #1

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

样例输出 #1

Yes
No
Yes

提示

【输入输出样例 1 1 1 说明】

第一组数据,由奶酪的剖面图可见:

第一个空洞在 ( 0 , 0 , 0 ) (0,0,0) (0,0,0) 与下表面相切;

第二个空洞在 ( 0 , 0 , 4 ) (0,0,4) (0,0,4) 与上表面相切;

两个空洞在 ( 0 , 0 , 2 ) (0,0,2) (0,0,2) 相切。

输出 Yes

第二组数据,由奶酪的剖面图可见:

两个空洞既不相交也不相切。

输出 No

第三组数据,由奶酪的剖面图可见:

两个空洞相交,且与上下表面相切或相交。

输出 Yes

【数据规模与约定】

对于 20 % 20\% 20% 的数据, n = 1 n = 1 n=1 1 ≤ h 1 \le h 1h r ≤ 1 0 4 r \le 10^4 r104,坐标的绝对值不超过 1 0 4 10^4 104

对于 40 % 40\% 40% 的数据, 1 ≤ n ≤ 8 1 \le n \le 8 1n8 1 ≤ h 1 \le h 1h r ≤ 1 0 4 r \le 10^4 r104,坐标的绝对值不超过 1 0 4 10^4 104

对于 80 % 80\% 80% 的数据, 1 ≤ n ≤ 1 0 3 1 \le n \le 10^3 1n103 1 ≤ h , r ≤ 1 0 4 1 \le h , r \le 10^4 1h,r104,坐标的绝对值不超过 1 0 4 10^4 104

对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 1 × 1 0 3 1 \le n \le 1\times 10^3 1n1×103 1 ≤ h , r ≤ 1 0 9 1 \le h , r \le 10^9 1h,r109 T ≤ 20 T \le 20 T20,坐标的绝对值不超过 1 0 9 10^9 109

解题思路:

这里就不介绍并查集了,放一篇我写的并查集传送门qwq,传送门

为什么本题用并查集解决呢?

我们可以对题意进行抽象,把所有与底层连通的空洞作为起点集合,把所有与顶层连通的空洞作为终点集合

那么我们遍历尝试将所有可能的两个点合并为一个集合

最后检查起点集合和终点集合会不会合并即可

引用一句从别的地方学来的话:“凡是涉及到元素的分组管理,都可以考虑用并查集来维护。”

AC代码如下

//并查集
#include <iostream>
#include <cmath>
using namespace std;
const int max_n = 1e3;

int fa[max_n + 3] = { 0 };//根节点
int dep[max_n + 3] = { 0 };//深度
struct node { int x, y, z; }nodes[max_n + 3];//存图

//初始化
void init(int n) {
	for (int i = 1; i <= n; i++) {
		fa[i] = i;
		dep[i] = 1;
	}
}

//寻访根节点
int find(int x) {
	return fa[x] == x ? x : (fa[x] = find(fa[x]));
}

//按秩合并
void merge(int x, int y) {
	int t1 = find(x);
	int t2 = find(y);
	if (dep[t1] >= dep[t2]) fa[t2] = fa[t1];
	else fa[t1] = fa[t2];

	if (dep[t1] == dep[t2]) dep[t1]++;
}

//是否相通
bool is_connect(int x, int y, const long long& r) {
	long long distance = (long long)(nodes[x].x - nodes[y].x) * (long long)(nodes[x].x - nodes[y].x) +
		(long long)(nodes[x].y - nodes[y].y) * (long long)(nodes[x].y - nodes[y].y) +
		(long long)(nodes[x].z - nodes[y].z) * (long long)(nodes[x].z - nodes[y].z);
	if (4l * r * r >= distance) return true;
	else return false;
}

//是否同集合
bool is_same_tree(int x, int y) {
	if (find(x) == find(y)) return true;
	else return false;
}

int main() {
	int T, n, h, r, x, y, z;
	bool special;
	cin >> T;
	for (int i = 0; i < T; i++) {
		cin >> n >> h >> r;
		//初始化
		special = false;
		fa[0] = 0; dep[0] = 1;
		nodes[0] = { 0,0,0 };//底层
		fa[n + 1] = n + 1; dep[n + 1] = 1;
		nodes[n + 1] = { 0,0,h };//顶层
		init(n);
		for (int j = 1; j <= n; j++) {//存图
			cin >> x >> y >> z;
			nodes[j] = { x,y,z };
			if (z + r >= h) fa[j] = fa[n + 1];
			if (z - r <= 0) fa[j] = fa[0];
			if (z + r >= h && z - r <= 0) {//特判
				special = true;
			}
		}
		if (special) {
			cout << "Yes" << endl;
			continue;
		}

		//合并
		for (int j = 1; j <= n - 1; j++) {
			for (int z = j + 1; z <= n; z++) {
				if (!is_same_tree(j, z) && is_connect(j, z, r)) merge(j, z);
			}
		}

		if (find(0) == find(n + 1)) cout << "Yes" << endl;
		else cout << "No" << endl;
	}
	return 0;
}

这里加上特判,原因是如果所有的空洞都是上下贯通,那么所有的空洞都会归于起点集合,导致结果错误

[HAOI2006]聪明的猴子(C++,最小生成树)

题目描述

在一个热带雨林中生存着一群猴子,它们以树上的果子为生。昨天下了一场大雨,现在雨过天晴,但整个雨林的地表还是被大水淹没着,部分植物的树冠露在水面上。猴子不会游泳,但跳跃能力比较强,它们仍然可以在露出水面的不同树冠上来回穿梭,以找到喜欢吃的果实。

现在,在这个地区露出水面的有N棵树,假设每棵树本身的直径都很小,可以忽略不计。我们在这块区域上建立直角坐标系,则每一棵树的位置由其所对应的坐标表示(任意两棵树的坐标都不相同)。

在这个地区住着的猴子有M个,下雨时,它们都躲到了茂密高大的树冠中,没有被大水冲走。由于各个猴子的年龄不同、身体素质不同,它们跳跃的能力不同。有的猴子跳跃的距离比较远(当然也可以跳到较近的树上),而有些猴子跳跃的距离就比较近。这些猴子非常聪明,它们通过目测就可以准确地判断出自己能否跳到对面的树上。

【问题】现已知猴子的数量及每一个猴子的最大跳跃距离,还知道露出水面的每一棵树的坐标,你的任务是统计有多少个猴子可以在这个地区露出水面的所有树冠上觅食。

输入格式

输入文件monkey.in包括:

第1行为一个整数,表示猴子的个数M(2<=M<=500);

第2行为M个整数,依次表示猴子的最大跳跃距离(每个整数值在1–1000之间);

第3行为一个整数表示树的总棵数N(2<=N<=1000);

第4行至第N+3行为N棵树的坐标(横纵坐标均为整数,范围为:-1000–1000)。

(同一行的整数间用空格分开)

输出格式

输出文件monkey.out包括一个整数,表示可以在这个地区的所有树冠上觅食的猴子数。

样例 #1

样例输入 #1

4
1 2 3 4
6
0 0
1 0
1 2
-1 -1
-2 0
2 2

样例输出 #1

3

提示

【数据规模】

对于40%的数据,保证有2<=N <=100,1<=M<=100

对于全部的数据,保证有2<=N <= 1000,1<=M=500

感谢@charlie003 修正数据

解题思路:

根据题目描述,要求其实就是使两节点之间的最大距离尽可能的小,然后统计跳跃距离不小于这个最大距离的猴子数量

也就是最小生成树

接下来介绍prim算法

prim算法的本质是贪心

基本思想:首先任意选一个节点加入树中

之后循环加入距离树最近的节点

思想很好理解吧,接下来介绍如何实现

区分加入树和没有加入树的节点很容易实现,通过一个布尔数组即可

如何获取到树的最近节点是关键

要知道到树的最近节点,就要知道每一个节点到树的最短距离

所以开一个数组维护最短距离

每加入一个新节点,尝试用该新节点去更新最短距离数组

然后从更新后的数组选出距离树最近的节点加入,如此循环即可

与常规图不同的是,这里的任意两个节点之间都可以看作相连的,但是区别也不大

本题可以算得上是prim算法的板题了

AC代码如下

//最小生成树prim
#include <iostream>
#include <algorithm>
#include <memory.h>
using namespace std;
const int max_m = 500;
const int max_n = 1000;
const int NaN = 0x3F3F3F3F;

int jump[max_m + 1] = { 0 };
struct tree { int x, y; }trees[max_n + 1];
bool book[max_n + 1] = { false };
int dist[max_n + 1] = { 0 };

int calc_dist(tree& t_1, tree& t_2) {
	return (t_1.x - t_2.x) * (t_1.x - t_2.x) +
		(t_1.y - t_2.y) * (t_1.y - t_2.y);
}

void prim(const int& n) {
	int node = 1;
	int temp = node;
	dist[node] = 0;//初始化
	
	for (int i = 1; i <= n; i++) {//最小生成树
		int min_dist = NaN;//初始化
		for (int j = 1; j <= n; j++) {
			if (!book[j]) {//未加入最小生成树
				dist[j] = min(dist[j], calc_dist(trees[node], trees[j]));//更新到树的最小距离
				if (dist[j] < min_dist) {
					temp = j;
					min_dist = dist[j];
				}
			}
		}
		node = temp;
		book[node] = true;//加入最小生成树
	}
}

int main() {
	memset(dist, 0x3F, sizeof(int) * (max_n + 1));//初始化
	int m, n, x, y;
	cin >> m;
	for (int i = 1; i <= m; i++) cin >> jump[i];
	cin >> n;
	for (int i = 1; i <= n; i++) {//存图
		cin >> x >> y;
		trees[i] = { x,y };
	}
	prim(n);
	sort(dist + 1, dist + n + 1);
	sort(jump + 1, jump + m + 1);
	int i;
	for (i = 1; i <= m; i++) {
		if (jump[i] * jump[i] >= dist[n]) break;
	}
	cout << m - i + 1;
	return 0;
}

这里由于为了避免开根号带来的精度误差,选择存储平方值
(反正不会溢出)

如果学习过Dijkstra算法,这里再说明一下prim和Dijkstra的不同之处

Dijkstra是要求从一个节点到达所有节点的路径最短,并不关心每条边的长度

prim是要求每条边最短,并不关心最短路径

举一个简单的例子

现在有 10 10 10个节点按顺序围成一个环,除了 1 1 1 10 10 10的距离为 5 5 5,每两个节点之间的距离都是 1 1 1

现在如果是Dijkstra,获取 1 1 1 10 10 10的距离应该为 5 5 5,

而prim生成的最小生成树中这个距离为 9 9 9

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/143190.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于Java+SpringBoot+vue+element实现校园闲置物品交易网站

基于JavaSpringBootvueelement实现校园闲置物品交易网站 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 欢迎点赞 收藏 ⭐留言 文末获取源码联系方式 文章目录基于JavaSpri…

Element UI 走马灯的使用

目录 走马灯是什么 原生js实现 Element UI的走马灯使用 el-carousel Carousel Events el-carousel-item 走马灯是什么 在有限空间内&#xff0c;循环播放同一类型的图片、文字等内容&#xff0c;走马灯也叫轮播图。 比如 原生js实现 JS实现轮播图效果&#xff08;同时…

mysql学习总结二

聚合函数 聚合函数表示对 值的集合 进行操作的 组&#xff08;集合&#xff09;函数。 # 华为手机价格的平均值 SELECT AVG(price) FROM products WHERE brand 华为; # 计算所有手机的平均分 SELECT AVG(score) FROM products; # 手机中最低和最高分数 SELECT MAX(score) FR…

SpringBoot解决全局和局部跨域问题的两种方式

前言 在如今前后端分离的开发模式下&#xff0c;跨域是一个非常经典的问题&#xff0c;解决的方式也有很多&#xff0c;比如代理服务器&#xff0c;使用JSONP 我之前也写过一篇解决跨域问题的文章&#xff0c;感兴趣的可以参考&#xff1a;解决Vue前后端跨域问题的多种方式 …

【现代机器人学】学习笔记九:运动规划

本节和前一节 【现代机器人学】学习笔记八&#xff1a;轨迹生成 不同&#xff0c;侧重于避障的内容。有一些我认为的重要的基本的概念&#xff1a;1.路径规划是一个纯几何问题&#xff0c;寻找一条无碰撞路径&#xff0c;不涉及动力学和时间相关内容。因此路径规划是运动规划的…

请查收 | 2022 阿里妈妈技术文章回顾

新年伊始&#xff0c;万象更新&#xff01;转眼&#xff0c;阿里妈妈技术已陪伴大家走过601天~在此&#xff0c;感谢每位读者朋友的支持与关注回顾2022&#xff0c;我们分享了60篇原创技术文章、发布了1本营销科学系列白皮书、开源了1项向量召回技术方案&#xff1b;阿里妈妈营…

Netty基础入门——NIO【1】

Netty基础入门——NIO【1】 1 NIO 1.1 三大组件 1.1.1 Channel && Buffer Channle channel类似于stream&#xff0c;是读写数据的双向通道&#xff0c;而stream要么是输入要么是输出 #mermaid-svg-9w1vFFYCVQmRvHja {font-family:"trebuchet ms",verdana…

YACC移进规约冲突案例分析

总结 总结&#xff1a; bison给出的用例是发现冲突的最便捷方法。 第一种用例&#xff1a;明确用例&#xff08;一个Example&#xff09;&#xff0c;直接反应问题。第二种用例&#xff1a;混淆用例&#xff08;两个Example&#xff09;&#xff0c;解析器无法区分两条语句。…

jenkins 节点部署

1、节点注册 登陆jenkins master界面 路径&#xff1a;首页-->系统管理--> 节点管理-->新建节点&#xff08;New Node&#xff09; 插曲&#xff1a;我在新的服务器部署master节点&#xff0c;显示剩余交换空间为0B 处理方式请查看&#xff1a;Jenkins - Free Swap…

关于MCU的BootLoader的一些理解

一、关于STM32单片机IAP升级中if(((*(__IO uint32_t*)Addr_App) & 0x2FFE0000) 0x20000000)语句的理解 参考自&#xff1a;https://blog.csdn.net/weixin_45394120/article/details/122732203?spm1001.2014.3001.5502 疑问&#xff1a; 1、为什么要用Addr_App里的数据…

Web操作系统漏洞发现——工具使用总结

目录 &#xff08;一&#xff09;web层面 1、信息收集 0x01 网站源码自己开发 0x02 网站源码使用开源CMS 2、可维护Poc 0x01 pocassist 0x02 afrog 3、APP渗透 0x01 在BP上添加转发端口 0x02 Xray进行监听 0x03 触发数据 4、Goby &#xff08;二&#xff09;操作系统层…

xss.haozi靶场通关

做完xss-labs靶场后&#xff0c;再继续做这个靶场&#xff0c;感觉这个不是很难&#xff0c;毕竟在第一个靶场也获取了一些经验&#xff0c;但是这个靶场偏向技巧&#xff0c;所以还是以了解为主。 0x00: 分析&#xff1a;对我们的代码未作出限制&#xff0c;因此这里可以使用…

如何用 nodejs 进行 sha1 加密验证,微信公众号开发验证

如何用 nodejs 进行 sha1 加密验证&#xff0c;微信公众号开发验证 一、问题 今天在摆弄微信公众号的时候&#xff0c;遇到这样一个问题&#xff1a; 我的后台是 nodejs 写的&#xff0c;express 框架&#xff0c;官方开发接入的验证代码是 php 写的&#xff0c;其中有一个部…

C语言之蓝桥杯习题(3)☞暴力求解版(思路写在解题过程中)

第一题.1.问题&#xff1a;小蓝数字卡片题小蓝有很多数字卡片&#xff0c;每张卡片上都是数字0到9。 小蓝准备用这些卡片来拼一些数&#xff0c;他想从1开始拼现在小蓝手里有0到9的卡片各2021张&#xff0c;共20210张&#xff0c;请问小蓝可以从1拼到多少?2.解题过程&#xff…

【Docker】(三)使用registry远程镜像仓库管理镜像

1.前言 本系列文章记录了从0开始学习Docker的过程&#xff0c;Docker系列历史文章&#xff1a; &#xff08;一&#xff09;基本概念与安装使用 &#xff08;二&#xff09;如何使用Docker发布一个SpringBoot服务 在上一篇中留下了一个问题&#xff0c;使用Docker发布服务的方…

【开源代码 | MATLAB线性阵列仿真】

本文编辑&#xff1a;调皮哥的小助理 1、16阵元均匀线阵方向图 %8阵元均匀线阵方向图&#xff0c;来波方向为0度 clc; clear all; close all; element_num16;%阵元数为16 d_lamda1/2;%阵元间距d与波长lamda的关系 thetalinspace(-pi/2,pi/2,200); theta0[0.2 0.1];%来波方向 w…

systemd wsl 测试笔记

文章目录systemd 简介WSL systemdsystemctljournalctlhello serviceSleep 与 Timeout 测试Requires 测试After 测试systemd 简介 Linux 从关闭到运行, 完整的启动和启动过程有三个主要部分: 硬件启动(Hardware boot): 初始化系统硬件Linux 引导(Linux boot): 加载 Linux 内核&…

基于ERNIELayoutPDFplumber-UIEX的多方案学术论文信息抽取

本项目链接&#xff1a;https://aistudio.baidu.com/aistudio/projectdetail/5196032?contributionType1 0.问题描述 可以参考issue&#xff1a; ERNIE-Layout在&#xff08;人名和邮箱&#xff09;信息抽取的诸多问题阐述#4031 ERNIE-Layout因为看到功能比较强大就尝试了一…

Linux安装mongodb集群整合SpringBoot

一、Mongodb集群安装 本文介绍基于mongodb的副本机制搭建集群 192.168.139.186CentOS Linux release 7.7.1908 (Core)192.168.139.187CentOS Linux release 7.7.1908 (Core)192.168.139.188CentOS Linux release 7.7.1908 (Core) 准备工作 关闭selinux&#xff0c;关闭防火墙…

近场通信到2027年将达到467.81亿美元

2020年&#xff0c;全球近场通信市场规模为178.75亿美元&#xff0c;预计到2027年将达到467.81亿美元&#xff0c;2021年至2027年的CAGR为14.8%。这是根据Market Statsville Group (MSG)的一份新报告得出的。 近场通信(NFC)是基于无线接口的一系列协议&#xff0c;使得通信设备…