PAT甲级 并查集 相关题_C++题解
并查集
PAT (Advanced Level) Practice 并查集 相关题
- 《算法笔记》 重点摘要
- 1034 Head of a Gang (30)
- 1107 Social Clusters (30)
- 1118 Birds in Forest (25)
《算法笔记》 9.6 并查集 重点摘要
1. 定义
father[i]
表示元素 i的父结点father[i] = i
表示元素 i 为该集合根结点- 每个集合只存在一个根结点,且其作为所属集合的标识
int father[N];
2. 初始化
最初每个元素都是一个独立的集合
for (int i = 0; i <= n; i++)
father[i] = i;
3. 查找
对给定结点寻找其根结点:反复寻找父结点直到根结点
- 递推实现
int findFather (int x){
while (x != father[x])
x = father[x];
return x;
}
- 递归实现
int findFather (int x){
if (x == father[x]) return x;
else return findFather(father[x]);
}
4. 合并
- 判断两元素是否在同一集合
- 分别查找两根结点
- 判断根结点是否相同
- 合并集合:其中一个根结点的父节点指向另一个根结点
- 性质:只对不同集合进行合并,保证同一个集合中不会产生环,即并查集每个集合都是一棵树
void Union (int a, int b){
int faA = findFather(a);
int faB = findFather(b);
if (faA != faB) father[faA] = faB;
}
5. 路径压缩
将当前结点查询路径上所有结点的父节点都指向根节点
- 递推实现
int findFather (int x){
int y = x; // 将初始查询结点记录下来
while (x != father[x]) // 用 x 进行回溯
x = father[x]; // 找到 x 对应根结点记录在 x 里
while (y != father[y]){ // 用记录的初始查询结点重新回溯
int z = y; //将此结点记录下来
y = father[y]; //向父结点回溯
father[z] = x; //将当前结点指向根结点
}
return x;
}
- 递归实现
int findFather (int x){
if (x == father[x]) return x;
else{
int F = findFather(father[x]);
father[x] = F;
return F;
}
}
1034 Head of a Gang (30)
题目思路
- 未作哈希函数,直接用
map<string,string>
存储父子关系,用map<string,int>
存储结点对应参数 - 输入时将通话时长记录在个人对应的权重 map 中
- 若此人未出现过 (
father.find(name1) == father.end()
),将独立成一个新集合,将其加入 map 并进行初始化father[name1] = name1
- 若已经出现过,不必初始化,直接与另一个通话对象 Union 起来
- 在 Union 函数中,首先查找两个人对应的头目
- 每加入一个新人就会调用 Union 函数,也就会查找其头目
- 查找头目时,要保证头目是这个集合中权重最高的
- 那么每次找到其头目后,要比较头目与新人的权值,若新人权值大,应设置为新的头目
- 这样每次 Union 得到的都是两个人对应团伙中权值最大的头目,将权值更大的作为新的头目
- 边输入边处理于是输入完成后可找到每个人对应的头目,遍历一次将团伙总通话时长和总人数分别与头目对应存储起来
- 最后按标准筛选出符合要求的团伙,放到 map 中
- 先输出符合要求的团伙数量即对应 map.size() 再按要求输出团伙头目和团伙人数
#include<iostream>
#include<map>
using namespace std;
map<string,string> father;
map<string,int> weight, totaltime, membernum, head_num;
string findFather(string s){
string t = s;
while (father[s] != s) s = father[s];
if (weight[s] < weight[t]){
father[s] = t;
father[t] = t;
return t;
}
return s;
}
void Union(string s1, string s2){
string fa1 = findFather(s1);
string fa2 = findFather(s2);
if (weight[fa1] > weight[fa2]) father[fa2] = fa1;
else father[fa1] = fa2;
}
int main()
{
int n, k, time;
scanf("%d%d", &n, &k);
string name1, name2;
for (int i = 0; i < n; i++){
cin >> name1 >> name2 >> time;
weight[name1] += time;
weight[name2] += time;
if (father.find(name1) == father.end()) father[name1] = name1;
if (father.find(name2) == father.end()) father[name2] = name2;
Union(name1, name2);
}
for (auto it: father){
totaltime[findFather(it.first)] += weight[it.first];
membernum[findFather(it.first)]++;
}
for (auto it: membernum)
if (it.second > 2 && totaltime[it.first] / 2 > k) head_num[it.first] = it.second;
cout << head_num.size() << endl;
for (auto it: head_num) cout << it.first << " " << it.second << endl;
return 0;
}
简化前 findFather
函数
string findFather(string s){
while (father[s] != s){
string fa = father[s];
if (weight[fa] < weight[s]){
if (father[fa] == fa) father[s] = s;
else father[s] = father[fa];
father[fa] = s;
}
else s = father[s];
}
return s;
}
1107 Social Clusters (30)
题目思路
- 用 set 数组保存每个人的兴趣爱好
- 输入完成后遍历 set 数组,将每个与之前的人的爱好 set 有重合的进行合并
- 合并处理完成后,遍历 father 数组,记录根结点即集合数,给每个结点对应根结点记录的集合人数 ++
- 将 map 按值排序,成对放入 vector,写 cmp 函数实现,最后按要求输出
- 注意:合并时,要对此人之前所有人的兴趣集合进行检查,不能检查到有相同的就停下,有三个点过不去。
问题在于如果前面有一个人兴趣为{3},一个人兴趣为{5},现在这个人兴趣为{3,5},在处理这个人之前,前两个人并不会被放入同一个 cluster 中,只有将现在这个人与前两个人分别合并,他们三人才能进入同一个 cluster
#include<iostream>
#include<set>
#include<algorithm>
#include<vector>
#include<unordered_map>
using namespace std;
int father[1001];
set<int> hobbies[1001];
bool cmp (pair<int,int> a, pair<int,int> b){ return a.second > b.second; }
int findFather(int x){
while (x != father[x]) x = father[x];
return x;
}
void Union(int a, int b){
int faA = findFather(a);
int faB = findFather(b);
if (faA != faB) father[faA] = faB;
}
int main()
{
for (int i = 0; i < 1001; i++) father[i] = i;
int n, k, hobby, clusternum = 0;
scanf("%d", &n);
for (int i = 0; i < n; i++){
scanf("%d: ", &k);
for (int j = 0; j < k; j++){
scanf("%d", &hobby);
hobbies[i].insert(hobby);
}
}
for (int i = 1; i < n; i++)
for (int j = 0; j < i; j++)
for (auto it: hobbies[i])
if (hobbies[j].find(it) != hobbies[j].end()) Union(i,j);
unordered_map<int,int> peoplenum;
for (int i = 0; i < n; i++){
if (father[i] == i) clusternum++;
peoplenum[findFather(i)]++;
}
vector<pair<int,int>> sortnum;
for (auto it: peoplenum) sortnum.push_back({it.first,it.second});
sort(sortnum.begin(),sortnum.end(),cmp);
printf("%d\n%d",clusternum,sortnum[0].second);
for (int i = 1; i < sortnum.size(); i++) printf(" %d",sortnum[i].second);
return 0;
}
1118 Birds in Forest (25)
题目思路
- 鸟编号从 1 开始,father 数组大小要开到 10001,并查集初始化
father[i] = i
- 每输入一幅画中鸟的数量,先接收输入第一只鸟的编号,再循环接收其他的鸟,将后输入的鸟与第一只鸟合并
- 用 set 记录鸟的数量,每输入一只鸟就压入 set,set 会自动去重,输入完成后 set 的大小即为鸟的数量
- 由于鸟编号连续,由 set 大小即可知 father 数组记录到了哪里
- 遍历记录了鸟信息的部分 father 数组,找到
father[i+1] == i+1
的即为根节点,根结点树即为集合数也即为树的棵数 - 最后看两只鸟是否在同一棵树,分别查询两只鸟的根结点,比较是否相等即可
#include<iostream>
#include<set>
using namespace std;
int father[10001];
int findFather(int x){
while (x != father[x]) x = father[x];
return x;
}
void Union(int a, int b){
int faA = findFather(a);
int faB = findFather(b);
if (faA != faB) father[faA] = faB;
}
int main()
{
for (int i = 0; i < 10001; i++) father[i] = i;
int n, k, q, q1, q2, fbird, bird, treenum = 0;
scanf("%d", &n);
set<int> birds;
for (int i = 0; i < n; i++){
scanf("%d%d", &k, &fbird);
birds.insert(fbird);
for (int j = 0; j < k - 1; j++){
scanf("%d", &bird);
birds.insert(bird);
Union(fbird,bird);
}
}
for (int i = 0; i < birds.size(); i++)
if (father[i+1] == i+1) treenum++;
printf("%d %d\n", treenum, birds.size());
scanf("%d", &q);
for (int i = 0; i < q; i++){
scanf("%d%d", &q1, &q2);
printf("%s\n", findFather(q1) == findFather(q2) ? "Yes" : "No");
}
return 0;
}