题意:
小A同学被确诊为新冠感染,需要尽快找到所有和小A同学直接或者间接接触过的同学,将他们隔离,防止更大范围的扩散。学生的交际可能是分小团体的,一位学生可能同时参与多个小团体内。
输入:
多组数据,对于每组测试数据:
第一行为两个整数n和m(n = m = 0表示输入结束,不需要处理),n是学生的数量,m是学生群体的数量。0 < n <= 3e4 , 0 <= m <= 5e2
学生编号为0~n-1
小A编号为0
随后,m行,每行有一个整数num即小团体人员数量。随后有num个整数代表这个小团体的学生。
输出:
输出要隔离的人数,每组数据的答案输出占一行
输入样例:
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
解题思路:
典型的并查集应用问题,并查集主要用par数组实现,记录每一个元素的par(通过压缩最后能变成其所在集合的代表元素)。并查集初始化ini( )使每个元素属于一个并查集,代表元是他们自己。找到代表元函数find通过par一直向上找到代表元(代表元的par是他们自己)。在这个过程中可以使用路径压缩,通过压缩最后par的值能变成其所在集合的代表元素。可以专门设立一个并查集大小的数组rank记录每个并查集的元素个数,用来在unite时使得小树接到大树下(大树成为小树的根)。这样的rank函数也有利用后面计算与0号元素在同一个并查集的元素个数。当然也可以重写count函数来计算与0号元素在同一个并查集的元素个数。每一个小团体组成一个并查集,有相同元素的交叉并查集应该进行unite成为同一个并查集。
注意事项:
1、元素x的par元素并不一定是其所在集合的代表元素,只能说通过压缩最后可能会变成其所在集合的代表元素。但find(x)的返回值一定是其所在集合的代表元素,而且只要经过了find(x)的操作以后,元素x的par元素就一定是其所在集合的代表元素了。
2、在读入小团体元素并进行unite时,可以设置一个last标志,只在第二次即以后读入数据时进行unite,也可先单独读入一个数据,计数值减1,然后再进入循环读入之后的数据并进行unite操作。
总结:
简单的并查集应用,所有会用到并查集的问题(如Kruskal最小生成树算法),都可以用这样的par数组和find、unite等操作配合路径压缩、rank数组等实现。
参考代码:
#include <iostream>
using namespace std;
int n,m;
int par[30010];
void ini(int n){
for (int i=0; i<n; i++) {
par[i]=i;
}
}
int find(int x){
if(par[x]==x)return x;
else return par[x]=find(par[x]);
}
bool unite(int x,int y){
x=find(x);
y=find(y);
if(x==y)return false;
par[x]=y;
return true;
}
int count(int x){
x=find(x);
int c=0;
for(int i=0;i<n;i++)
if(find(i)==x)
c++;
return c;
}
int main(int argc, const char * argv[]) {
while (true) {
cin>>n>>m;
if(n==0&&m==0)break;
ini(n);
while (m--) {
int num;cin>>num;
int x;cin>>x;
num--;
while (num--) {
int y;cin>>y;
unite(x, y);
}
}
cout<<count(0)<<endl;
}
return 0;
}