NEUQ-ACM预备队训练-week10(图论)

T1.P1636 Einstein学画画

洛谷原题

题目描述

Einstein 学起了画画。

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

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

输入格式

第一行两个整数 n, m。

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

一条边不会被描述多次。

输出格式

一个数,即问题的答案。

输入输出样例

输入

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

输出

1

其实是一笔画问题,关于一个图形能不能一笔画出来早有解答,即从相邻边的条数为奇数的点有两个或者零个的时候可以一笔画出来,而这道题问的是几笔,所以我们只需要把相邻边条数为奇数的点统计一下,因为每两个点可以一笔画出来,所以让这个数字/2即为所求,注意一下点数为0的特判就可以AC了。

#include <iostream>
using namespace std;

int main() {
	bool point[1005] = {0};
	int n, m;
	cin >> n >> m;
	int x, y;
	for (int i = 0; i < m; i++) {
		cin >> x >> y;
		point[x] = !point[x];
		point[y] = !point[y];
	}
	int ans = 0;
	for (int i = 1; i <= n; i++) {
		ans += point[i];
	}
	ans /= 2;
	if (ans)
		cout << ans << endl;
	else
		cout << 1 << endl;
	return 0;
}

T2.P8654 [蓝桥杯 2017 国 C] 合根植物

洛谷原题

题目描述

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

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

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

输入格式

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

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

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

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

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

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

输出格式

一行一个整数,表示答案

输入输出样例

输入

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

输出

5

说明/提示

样例解释

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

 是一道用并查集来解的题,两个植物合根可以看成是并为一个集合,每次输入都进行一次并的操作,输入完毕后遍历整个图看有几个集合,有几个集合就有几棵合根植物,输出这个数字即可

#include <iostream>
using namespace std;

int m, n;

int fa[1005][1005] = {0};

int fat(int e) {
	if (fa[(e - 1) / n][(e - 1) % n] == 0)
		return e;
	else
		return fat(fa[(e - 1) / n][(e - 1) % n]);
}

int main() {

	cin >> m >> n;

	int k;
	cin >> k;
	int t, e;
	for (int i = 0; i < k; i++) {
		cin >> t >> e;
		if (fat(e) == fat(t))
			continue;
		fa[(fat(e) - 1) / n][(fat(e) - 1) % n] = t;
	}
	int ans = 0;
	for (int i = 0; i < m; i++) {
		for (int j = 0; j < n; j++) {
			if (fa[i][j] == 0)
				ans++;
		}
	}
	cout << ans;
	return 0;
}

 T3.P3958 [NOIP2017 提高组] 奶酪

洛谷原题

题目描述

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

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

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

输入格式

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

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

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

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

输出格式

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

输入输出样例

输入

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

输出

Yes
No
Yes

说明/提示

【输入输出样例 1 说明】

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

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

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

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

输出 Yes

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

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

输出 No

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

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

输出 Yes

【数据规模与约定】

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

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

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

对于 100% 的数据,1≤n≤10^3,1≤h,r≤10^9,T≤20,坐标的绝对值不超过 10^9。

 有点难想,但还是一道并查集的题,基本思路就是一个个输入空洞的坐标之后,如果两个空洞相交或相切,就把他们归为一个集合,同时记录下所有的与下底面相连的空洞和所有与顶面相连的空洞,他们都有可能成为Jerry的出口或入口,当所有的球输入完毕后,对每一种入口和出口的情况遍历,若他们处于同一集合,则输出Yes,若任何一种情况都不属于同一集合,则输出No,代码实现如下

#include <iostream>
using namespace std;

int fa[1005] = {0};
int x[1005] = {0}, y[1005] = {0}, z[1005] = {0};
int top[1005], pot[1005];
int tot1 = 0, tot2 = 0;
long long n, h, r;

int fat(int e) {
	if (fa[e] == e)
		return e;
	else
		return fat(fa[e]);
}

bool banana(int i, int j) {
	long long a = x[i] - x[j], b = y[i] - y[j], c = z[i] - z[j];
	if (a * a + b * b + c * c <= 4 * r * r)
		return true;
	else
		return false;
}

int main() {
	int T;
	cin >> T;
	for (int o = 0; o < T; o++) {
		cin >> n >> h >> r;
		for (int i = 1; i <= n; i++) {
			fa[i] = i;
		}
		for (int i = 1; i <= n; i++) {
			cin >> x[i] >> y[i] >> z[i];
			if (z[i] + r >= h) {
				top[tot1++] = i;
			}
			if (z[i] - r <= 0) {
				pot[tot2++] = i;
			}
			for (int j = 1; j < i; j++) {
				if (banana(j, i))
					fa[fat(j)] = i;
			}
		}
		bool is = false;
		for (int i = 0; i < tot1; i++) {
			for (int j = 0; j < tot2; j++) {
				if (fat(top[i]) == fat(pot[j])) {
					is = true;
				}
			}
		}
		if (is)
			cout << "Yes" << endl;
		else
			cout << "No" << endl;
		tot1 = 0;
		tot2 = 0;
	}
	return 0;
}

T4.P1119 灾后重建

洛谷原题

题目背景

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

题目描述

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

输入格式

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

第二行包含NN个非负整数t0​,t1​,…,tN−1​,表示了每个村庄重建完成的时间,数据保证了t0​≤t1​≤…≤tN−1​。

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

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

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

输出格式

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

说明/提示

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

这道题实际上还是求最短路的问题,同时题目保证了询问时间和重建时间都是单调不减的,就是用floyed算法的一道题,floyed算法本质其实就是动态更新,把每种情况都试一遍后得出的最小值,按时间一个一个修复完毕的话,我们只需要把这个村庄加进去然后更新一遍数组即可,代码如下

#include <iostream>
using namespace std;

int N, M;
int dis[205][205];

void updata(int a) {
	for (int i = 0; i < N; i++) {
		for (int j = 0; j < N; j++) {
			if (dis[i][j] > dis[i][a] + dis[a][j]) {
				dis[i][j] = dis[i][a] + dis[a][j];
				dis[j][i] = dis[i][a] + dis[a][j];
			}
		}
	}
}

int main() {

	cin >> N >> M;
	int T[205];

	for (int i = 0; i < 205; i++) {
		for (int j = 0; j < 205; j++) {
			dis[i][j] = 1e9;
		}
	}
	for (int i = 0; i < N; i++) {
		cin >> T[i];
	}
	int t1, t2, t3;
	for (int i = 0; i < M; i++) {

		cin >> t1 >> t2 >> t3;
		dis[t1][t2] = t3;
		dis[t2][t1] = t3;
	}
	int now = 0;
	int q;
	cin >> q;
	for (int i = 0; i < q; i++) {
		cin >> t1 >> t2 >> t3;
		while (T[now] <= t3) {
			updata(now);
			now++;
		}
		if (t1 >= now || t2 >= now)
			cout << -1 << endl;
		else if (dis[t1][t2] == 1e9)
			cout << -1 << endl;
		else
			cout << dis[t1][t2] << endl;
	}
	return 0;
}

T5.P2504 [HAOI2006]聪明的猴子

洛谷原题

题目描述

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

现在,在这个地区露出水面的有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包括一个整数,表示可以在这个地区的所有树冠上觅食的猴子数。

输入输出样例

输入

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

输出

3

说明/提示

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

读懂题目的话很容易就能看出来我们需要找到连接这所有树的图的最大值,也就是最小生成树问题,我们把这个图按最小生成树转化完毕之后寻找路径的最大值,然后跟猴子最远跳跃的距离比较一下,如果跳跃的最远距离比这个最大值还大那肯定可以走过所有的树,反之则不能,代码如下

#include <iostream>
#include <algorithm>
using namespace std;

int monkey[100005];
int fa[100005];

struct Tree {
	int x;
	int y;
} tree[100005];

struct Edge {
	int q;
	int z;
	int dis;
} edge[1000005];

int fat(int k) {
	if (fa[k] == k)
		return k;
	return fa[k] = fat(fa[k]);
}

bool cmp(Edge a, Edge b) {
	return a.dis < b.dis;
}

int main() {
	int M, N;
	cin >> M;
	for (int i = 0; i < M; i++) {
		cin >> monkey[i];
	}
	cin >> N;
	for (int i = 0; i < N; i++) {
		cin >> tree[i].x >> tree[i].y;
	}
	for (int i = 0; i < N; i++) {
		fa[i] = i;
	}
	int cnt = 0;
	for (int i = 0; i < N; i++) {
		for (int j = 0; j < N; j++) {
			if (i == j)
				continue;
			edge[cnt].q = i;
			edge[cnt].z = j;
			edge[cnt++].dis = (tree[i].x - tree[j].x) * (tree[i].x - tree[j].x) + (tree[i].y - tree[j].y) * (tree[i].y - tree[j].y);
		}
	}
	int max = edge[0].dis;
	sort(edge, edge + cnt, cmp);
//	for (int i = 0; i < cnt; i++) {
//		cout << edge[i].q << ' ' << edge[i].z << ' ' << edge[i].dis << endl;
//	}
	int cut = N;
	for (int i = 0; i < cnt; i++) {
		if (cut == 1)
			break;
		if (fat(edge[i].q) == fat(edge[i].z))
			continue;
//		cout << edge[i].q << ' ' << edge[i].z << ' ' << edge[i].dis << endl;
		fa[fat(edge[i].q)] = fat(edge[i].z);
		cut--;
		max = edge[i].dis;
	}
//	cout << max << endl;
	int ans = 0;
	for (int i = 0; i < M; i++) {
		if (monkey[i]*monkey[i] >= max)
			ans++;
	}
	cout << ans;
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值