洛谷P1551-亲戚-题解
亲戚
题目背景
若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。
题目描述
规定: 和 是亲戚, 和 是亲戚,那么 和 也是亲戚。如果 , 是亲戚,那么 的亲戚都是 的亲戚, 的亲戚也都是 的亲戚。
输入格式
第一行:三个整数 ,(),分别表示有 个人, 个亲戚关系,询问 对亲戚关系。
以下 行:每行两个数 ,,,表示 和 具有亲戚关系。
接下来 行:每行两个数 ,询问 和 是否具有亲戚关系。
输出格式
行,每行一个 Yes 或 No。表示第 个询问的答案为“具有”或“不具有”亲戚关系。
样例 #1
样例输入 #1
6 5 3
1 2
1 5
3 4
5 2
1 3
1 4
2 3
5 6样例输出 #1
Yes
Yes
No思路
这题是 并查集 的模板题目。所谓并查集,就是一个具有合并和查询功能的森林,每一棵树都是是一个用父亲表示法的树,一个集合,这个集合里的元素具有某种关系。并查集的合并就是树和树之间合并的一个过程,而并查集的查询,我们都知道,在判断两个节点是否属于一棵树的时候,我们在可以使用判断他们的头节点是否相同来判断,并查集亦是如此。
知道这些基本概念,我们可以实现一个简单的并查集了。但需要注意的是,当我们森林中的树足够多的时候,频繁的合并可能会使查询的复杂度退化,为此,我们可以使用一些方法进行优化,这里直接贴出优化过的 并查集类模板 详细原理可以参考算法学习笔记(1) : 并查集
class UnionFindSet {
public:
vector<int> trees, size;
explicit UnionFindSet(int len) : trees(len), size(len, 1) {
iota(trees.begin(), trees.end(), 0);
}
int find(const int &x) {
int tmp;
for (tmp = x; trees[tmp] != tmp; tmp = trees[tmp]);
return tmp;
}
void unite(const int &x, const int &y) {
int &&fx = find(x), &&fy = find(y);
size[fx] <= size[fy] ? trees[fx] = fy : trees[fy] = fx;
if (size[fx] == size[fy] and fx != fy)
size[fy]++;
}
};这里用到了 iota 函数(注意不是 itoa )来快速填充了trees,填充范围为trees.size()
C++代码
#include <iostream>
#include <vector>
#include <numeric>
using namespace std;
class UnionFindSet {
public:
vector<int> trees, size;
explicit UnionFindSet(int len) : trees(len), size(len, 1) {
iota(trees.begin(), trees.end(), 0);
}
int find(const int &x) {
int tmp;
for (tmp = x; trees[tmp] != tmp; tmp = trees[tmp]);
return tmp;
}
void unite(const int &x, const int &y) {
int &&fx = find(x), &&fy = find(y);
size[fx] <= size[fy] ? trees[fx] = fy : trees[fy] = fx;
if (size[fx] == size[fy] and fx != fy)
size[fy]++;
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
int n, m, p, i, j;
cin >> n >> m >> p;
UnionFindSet ufs(n + 1);
while (m-- and cin >> i >> j)
ufs.unite(i, j);
while (p-- and cin >> i >> j)
cout << (ufs.find(i) == ufs.find(j) ? "Yes\n" : "No\n");
return 0;
}
洛谷P1551-亲戚-题解
https://winterl-blog.netlify.app/2023/08/01/洛谷P1551-亲戚-题解/