题意:
新型冠状病毒肺炎(Corona Virus Disease 2019,COVID-19),简称“新冠肺炎”,是指2019新型冠状病毒感染导致的肺炎。
如果一个感染者走入一个群体,那么这个群体需要被隔离!
小A同学被确诊为新冠感染,并且没有戴口罩!!!!!!
危!!!
时间紧迫!!!!
需要尽快找到所有和小A同学直接或者间接接触过的同学,将他们隔离,防止更大范围的扩散。
众所周知,学生的交际可能是分小团体的,一位学生可能同时参与多个小团体内。
请你编写程序解决!戴口罩!!
Input
多组数据,对于每组测试数据:
第一行为两个整数n和m(n = m = 0表示输入结束,不需要处理),n是学生的数量,m是学生群体的数量。0 < n <= 3e4 , 0 <= m <= 5e2
学生编号为0~n-1
小A编号为0
随后,m行,每行有一个整数num即小团体人员数量。随后有num个整数代表这个小团体的学生。
Output
输出要隔离的人数,每组数据的答案输出占一行
Sample Input
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
Sample Output
4
1
1
思路:
为了找到有多少人需要隔离,我们需要找到所有与小A有直接或间接接触的人数总和,我们可以采用并查集的思想,首先每个人都属于自己的小团体,然后随着题目输入一个个小团体,我们将输入中同在一个小团体的人加入同一个集合中,与小A有直接或间接接触的人就都在小A所在的集合中。
对于并查集的操作我们需要注意以下几点:
并查集的初始化:
首先每个人的父节点par[i]都是自己,即最初的时候每个人构成一个集合,每个集合内有一个人,每一个集合都需要采用其中的一个元素代表这个集合
查找某个人所在的集合即查找某人的父节点:
设立一个find函数,若是这个人的父节点就是自己,那么返回这个节点值,若不是,则递归调用find(par[i]),注意为了减少之后查询路途所耗时间,采用路径压缩的方法,将par[i] = find(par[i]),即将这个节点直接链接这个集合的代表元素的下面。
两个元素所在集合的合并:
首先调用find函数,找到两个元素分别隶属于哪一个集合,找到这两个集合的代表元素,若是相同,则表示这两个元素已经在一个集合里面了,无需合并,否则我们将小的集合连接在大的集合下面,这样可以减少以后find函数花的时间。
总结:
并查集的思想可以用来处理一些不相交集合的合并和查询问题,采用森林的形式存储,注意上面所说的处理并查集的几个问题。
代码:
#include<stdio.h>
#include<vector>
int n, m;
std::vector<int>par;
std::vector<int>rank;
int find(int i)
{
if (i == par[i])
return i;
else return par[i] = find(par[i]);
}
void unite(int i,int j)
{
int par1 = find(i);
int par2 = find(j);
if (par1 == par2)return;//****************************已经在一个并查集里了
if (rank[par1] < rank[par2])
{
rank[par2] += rank[par1];
par[par1] = par2;
}
else
{
rank[par1] += rank[par2];
par[par2] = par1;
}
}
int main()
{
while (scanf_s("%d %d", &n, &m) != EOF &&!(n == 0 && m==0) )
{
par = std::vector<int>(n);
rank = std::vector<int>(n);
for (int i = 0; i < n; i++)
{
par[i] = i;
rank[i] = 1;
}
for (int i = 0; i < m; i++)
{
int num;
scanf_s("%d", &num);
int studentbefore;
int nowstudent;
for (int k = 0; k < num; k++)
{
if (k == 0)
{
scanf_s("%d", &studentbefore);
continue;
}
scanf_s("%d", &nowstudent);
unite(studentbefore, nowstudent);
}
}
printf("%d\n", rank[find(0)]);
par.clear();
rank.clear();
}
}