ACM解题报告


 

 
 
                          The Suspects
Time Limit: 1000MS		Memory Limit: 20000K
Total Submissions: 9777		Accepted: 4642
Description
Severe acute respiratory syndrome (SARS), an atypical pneumonia of unknown aetiology, 
was recognized as a global threat in mid-March 2003. To minimize transmission to others,
 the best strategy is to separate the suspects from others. In the Not-Spreading-Your-
Sickness University (NSYSU), there are many student groups. Students in the same group
 intercommunicate with each other frequently, and a student may join several groups. 
To prevent the possible transmissions of SARS, the NSYSU collects the member lists of 
all student groups, and makes the following rule in their standard operation procedure 
(SOP). Once a member in a group is a suspect, all members in the group are suspects. 
However, they find that it is not easy to identify all the suspects when a student is 
recognized as a suspect. Your job is to write a program which finds all the suspects.
Input
The input file contains several cases. Each test case begins with two integers n and 
m in a line, where n is the number of students, and m is the number of groups. You 
may assume that 0 < n <= 30000 and 0 <= m <= 500. Every student is numbered by a 
unique integer between 0 and n−1, and initially student 0 is recognized as a suspect
 in all the cases. This line is followed by m member lists of the groups, one line 
per group. Each line begins with an integer k by itself representing the number of 
members in the group. Following the number of members, there are k integers 
representing the students in this group. All the integers in a line are separated
 by at least one space. A case with n = 0 and m = 0 indicates the end of the input,
 and need not be processed.
Output
For each case, output the number of suspects in one line.
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
/*先说题的意思是:这个学校里有m个班,整个一共有n个学生,如果这某组中有一个学生被检测成为sars嫌疑,那么这组所有成员都得隔离,每个学生可以加入很多组,。那么你的任务就是找出需要隔离的学生学号,每个学生都有一个唯一的学号Snum < n 题目规定假设 0 号学生已经被确定为sars嫌疑
好了现在我们来说下并查集

并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题 

主要操作
需要注意的是,一开始我们假设元素都是分别属于一个独立的集合里的。
合并两个不相交集合
操作很简单:先设置一个数组Father[x],表示x的“父亲”的编号。 那么,合并两个不相交集合的方法就是,找到其中一个集合最父亲的父亲(也就是最久远的祖先),将另外一个集合的最久远的祖先的父亲指向它。


 void Union(int x,int y)
{
    fx = getfather(x);
    fy = getfather(y);
    if(fy!=fx)
       father[fx]=fy;
}

判断两个元素是否属于同一集合
仍然使用上面的数组。则本操作即可转换为寻找两个元素的最久远祖先是否相同。可以采用递归实现。
 
 bool same(int x,int y)
{
   return getfather(x)==getfather(y);
}
/*返回true 表示相同根结点,返回false不相同*/

并查集的优化
路径压缩
刚才我们说过,寻找祖先时采用递归,但是一旦元素一多起来,或退化成一条链,每次GetFather都将会使用O(n)的复杂度,这显然不是我们想要的。
对此,我们必须要进行路径压缩,即我们找到最久远的祖先时“顺便”把它的子孙直接连接到它上面。这就是路径压缩了。使用路径压缩的代码如下,时间复杂度基本可以认为是常数的。
Function getfather(v:integer):integer;
  begin
    if (father[v]=0) then
      getfather:=v
    else
      begin
        father[v]:=getfather(father[v]);{路径压缩}
        getfather:=father[v];
      end;
  end;

Procedure Initialize;
var
  i:integer;
begin
  for i:=1 to maxv do 
    Father[i]:=i;
end;
 
Function GetFather(v:integer):integer;
begin
  if Father[v]=v then 
    exit(v) else
    Father[v]:=getfather(father[v]);{路径压缩}
  exit(Father[v]);
end;

Rank合并
合并时将元素所在深度低的集合合并到元素所在深度高的集合。
 
初始化:
fillchar(rank,sizeof(rank),0);

合并时将元素所在深度低的集合合并到元素所在深度深的集合。
void judge(int x ,int y)
 
{
     fx = getfather(x);
     fy = getfather(y);
 
     if (rank[fx]>rank[fy])
        father[fy] = fx;
     else
     {
        father[fx] = fy;
        if(rank[fx]==rank[fy])
           inc(rank[fy]);
     }
}

初始化:
memset(rank,0,sizeof(rank));

时间复杂度
O(n*α(n))
其中α(x),对于x=宇宙中原子数之和,α(x)不大于4
事实上,路经压缩后的并查集的复杂度是一个很小的常数。
源代码
加了所有优化的代码框架:
function getfather(v:longint):longint;
begin
  if father[v]=v then
    exit(v) else
     father[v]:=getfather(father[v]);
    exit(father[v]);
end;

下面是AC了的代码*/
#include <stdio.h>
#define max 30003
int father[max];
int find(int x){ 
/*if (x!=father[x]) 
  {
  father[x] = find(father[x]);//回来的时候把所有的子节点指向新的根节点上 
  }
   return father[x];*///这种递归费时间,但是会缩成多点对一点的情况 
   
   int t=x;//q全部成多点到一点集合
   while (father[x]!=x)
     x=father[x];//只是找到根节点
     father[t]=x;//然后把新加入的点指向 根//这种不费时间但是 是一个单树状 
     return x;
}
int Union(int x,int y)
{
 x = find (x);
 y = find (y);
 if(x==y)
   return 0;
 father[y] = x;
 return 0;
}
int main()
{
 //freopen("in.txt","r",stdin);
   int m,n;
 
   while (scanf("%d %d",&m,&n)!=EOF)   
  {
    if((!m) && (!n))break;
    int i = m;
    while(m>=0){
	father­=m;
	m--;
    }
    int gn,a;
    int b;
 
    while(n--){
 	 scanf("%d %d",&gn,&a);
	 while(--gn){
 	     scanf("%d ",&b);
	     Union(a,b);
	 }
    }
  int sum=1;
  int t =find(0);
  for(int j=1;j<i;j++)
     if(find(j)==t)sum++;
     printf("%d\n",sum);
 }
   return 0;
} 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值