问题C:COVID-19

问题 C: COVID-19

题目描述:
2020年,新冠肺炎席卷全球。
中国采取绝不放弃的方针,始终将人民的生命安全放在第一位,坚决捍卫每一位公民生命权的举动,赢得了全世界的赞扬。
在中国大陆进行的流行病学调查(以下简称“流调”)工作标准中,需要追查病例的密切接触者(以下简称“密接者”),做好隔离筛查工作。

在这里插入图片描述
南京的病例是一个辅导班老师,所以他教过的英语辅导班篮球辅导班的学生以及他们所接触的人都要被排查。
已知某高校有n个学生社团,由于学生社团的人员活动交流频繁,一旦有一个人被感染病毒,社团里的所有人以及他们参加的社团里的所有人都可能被感染,于是这些人都会被隔离检测。
我们习惯将第一个感染者称为0号病人。请你求出一个学校出现了0 号感染者之后,最终会有多少学生被隔离。

输入
输入包含多组测试用例。每个测试用例,第一行包含两个整数 n、m,n 表示学校学生人数,m 表示社团个数。假设 0 < n ≤ 30000,0 ≤ m ≤ 1000。每个学生从 0 到 n-1 编号。接下来是 m 行,每行开头是一个整数 k,表示该社团的学生个数,接着是 k 个整数表示该社团的学生编号。最后一个测试用例,n = 0、m = 0,表示输入结束。
输出
对应每组数据,输出一行,包括一个整数,表示当0号学生感染后最后感染病毒的学生人数。

样例输入
10 2
3 0 1 2
5 2 4 5 6 7

120 5
5 8 12 13 17 20
2 4 6
3 0 2 4
2 99 2
8 0 4 8 10 12 13 14 15

1 0

0 0

样例输出
7
13
1

提示
说明:第一组样例 10名学生2个社团 0号感染者感染了 0 1 2,2号感染了4 5 6 7因此一共会有0 1 2 4 5 6 7 共7人被隔离。

此题,笔者给出以下两种解题思路。(本次先更新法一的思路及代码)
法一:把每个学生看成集合中的元素,利用并查集将同一俱乐部的学生进行合并。等全部合并完毕后,通过Find()函数找到编号为0的学生所在的根结点,输出编号为0的学生的根结点的Num[]。(这边就一笔带过,具体操作见代码一)。

#include <stdio.h>

#define MAX 30001//学校学生人数n最多为30000。

int parent[MAX];//定义一个全局数组parent[],用来记录每个元素的根结点。
int Num[MAX];//定义一个全局数组Num[],用来记录根结点所在集合的元素个数。

/*初始化操作*/
void init(int n)
{
    
    
	int i;
	for(i=0;i<n;i++)
	{
    
    
		parent[i]=i;//将每个结点的父结点设置成自己(学生从0——n-1编号)。
		Num[i]=1;//将每个结点所在集合的元素个数设置为1。
	}
} 

/*查询元素的根结点操作*/
int Find(int i)
{
    
    
	if(parent[i]==i)//如果该元素的父结点就是其自己,则说明该元素为根结点,返回该元素的编号。
	 return i;
	else
	/*否则递归地去寻找该元素的根结点,
	递归操作顺便把从根到该结点路径上的所有结点都直接放到根节点下。*/
	 return parent[i]=Find(parent[i]);//压缩路径。
	 //补充:压缩路径的好处:有可能降低了树高,提高以后的查询效率。
}

/*集合合并操作*/
/*找到元素a的根结点A,元素b的根结点B,
如果元素a与元素b的根结点不同,则将A的父结点设置为B,并且更新以B为根结点的集合元素个数。*/
void Union(int a,int b)
{
    
    
	int A=Find(a);
	int B=Find(b);
    if(A!=B)
    {
    
    
    	parent[A]=B;
    	Num[B]+=Num[A];
	}
}

int main(int argc, char *argv[]) {
    
    
	int n,m,i,j,k,x,y;
	scanf("%d%d",&n,&m);//输入学校学生人数n和社团个数m。
	while(n!=0||m!=0)//输入结束条件n=0,m=0。
  {
    
     
    i=1;//初始化i为1.
    init(n);//对n个集合进行初始化(学生编号0——n-1,一开始将每个学生看成互不相交的单元素集合)
    
	while(i<=m)//m个社团。
	{
    
    
		scanf("%d%d",&k,&x);//输入社团人数k,和该社团中的第一个学生编号x。
		for(j=1;j<k;j++)//剩下k-1个该社团的学生,所以进行k-1次循环。
		{
    
    
			scanf("%d",&y);//输入该社团中的一个学生编号。
			Union(x,y);//将该生与第一个学生所在的集合进行合并操作。
		}
		i++;
	}
	int zero_root=Find(0);//找到编号为0的学生的根结点
	
	/*如果输入的社团个数为0,则输出1。
	举例:如果学校学生人数为1000,0个社团,0号学生感染了,则最后染病的学生人数为1。*/
	if(m==0)
	 printf("1");
	else//否则,输出编号为0的学生的根结点所在集合的个数。
	 printf("%d\n",Num[zero_root]);
	
	scanf("%d%d",&n,&m);
  }
  	return 0;
}

以上就是法一的思路及代码,下次更新讨巧的法二解法。小伙伴们,如果有更好地解题思路、疑问、建议可以在评论区里面告诉我。本次的分享就到这,感谢大家围观。

猜你喜欢

转载自blog.csdn.net/qq_46139801/article/details/113184509