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;
}
posted @ 2019-09-09 18:53  鲸90830  阅读(620)  评论(0编辑  收藏  举报