You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

111 lines
3.2 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include <bits/stdc++.h>
// https://www.cnblogs.com/yym2013/p/3845448.html
// https://www.cnblogs.com/zhxmdefj/p/11117791.html#4000267478
// 董晓老师讲解 按秩合并并查集
// https://www.bilibili.com/video/av838604930
/**
按秩合并的并查集
Q:为什么要将数量小的向数量大的连边,而不是倒过来?
A:1.通过比较并查集的大小来连边,将小的(u)向大的(v)连边这样对于大的并查集查询代价不变而小的并查集查询代价每个点增加了1
相当于增加了siz[u],(反过来则变成了siz[v]),siz[u]<siz[v]所以这样更优。
2.相较于路径压缩的并查集,他可以保留原始的信息。
*/
/**
// 上面是一采用递归的方式压缩路径, 但是递归压缩路径可能会造成溢出栈我曾经因为这个RE了n次
下面我们说一下非递归方式进行的路径压缩:
int find(int x){
int k, j, r;
r = x;
while(r != parent[r]) //查找跟节点
r = parent[r]; //找到跟节点用r记录下
k = x;
while(parent[k] != r) //非递归路径压缩操作
{
j = parent[k]; //用j暂存parent[k]的父节点
parent[k] = r; //parent[x]指向跟节点
k = j; //k移到父节点
}
return r; //返回根节点的值
}
*/
// https://www.cnblogs.com/ARTlover/p/9752355.html
using namespace std;
const int N = 30001;
int s[N]; //每个集合人数
int fa[N]; //并查集数组
int rnk[N]; //树高,不能使用rank变量名与std中的已定义概念冲突
/**
测试数据
100 4
2 1 2
5 10 13 11 12 14
2 0 1
2 99 2
200 2
1 5
5 1 2 3 4 5
1 0
0 0
答案:
4
1
1
*/
//查询+带路径压缩
int find(int x) {
if (x == fa[x]) return x;
else return fa[x] = find(fa[x]); //带路径压缩
}
//合并并查集
void join(int x, int y) {
//找出双方的根
int xRoot = find(x);
int yRoot = find(y);
//同根则结束
if (xRoot == yRoot) return;
//让rank比较高的作为父结点
if (rnk[xRoot] > rnk[yRoot]) {
fa[yRoot] = xRoot;
s[xRoot] += s[yRoot];//人数也需要合并进去
} else {//小于等于都进这里~
fa[xRoot] = yRoot;
if (rnk[xRoot] == rnk[yRoot]) rnk[yRoot]++; //树的高度是在这里修改的,是什么意思?看董晓老师的视频,讲解的很清楚
s[yRoot] += s[xRoot];//人数也需要合并进去
}
}
int n, m, k, x, y;
int main() {
while (cin >> n >> m) {
//输入为 0 0 结束
if (n == 0 && m == 0) return 0;
//初始化
for (int i = 0; i < n; i++) { //因为有0号学生所以数组遍历从0开始
fa[i] = i; //每个学生自己是自己的祖先
s[i] = 1; //集合中总人数初始值是1
rnk[i] = 0; //树的高度为0
}
for (int i = 0; i < m; i++) {
cin >> k >> x;
k--;
while (k--) {
cin >> y;
join(x, y);
}
}
cout << s[find(0)] << endl; //寻找0号学生相关集合的总人数
}
return 0;
}