#include // 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] 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; }