题目输入n和m,表示有n封邮件(题目没讲清楚是0~n-1还是1~n,但是看一下Sample,貌似是0~n-1,还有m次操作,最后要求我们输出邮件的类别数。
操作分两种,一种是M操作,后面跟两个操作数A和B,表示编号为A
和编号为B的两封邮件是同一类邮件;另外一种操作是S操作,后面跟一个操作数C,表示将编号为C的邮件从其当前所在类别U分离出来作为一类邮件,即把U分为U-{C}和{C}
很裸的并查集,M操作很容易实现,不多讲,主要讲一下S操作,我想到的有两种思路,一种是直接模拟,时间效率太低,故不讲,另一种思路是加多一封邮件,题目要求我们将邮件C从类别U中分离出来,但对类别U的组成不做要求,所以,我们可以加多一封邮件D,然后,把邮件C
映射到邮件D去,以后对邮件C的操作全部变成对邮件D的操作,这样,就相当于在不严格(不严格是因为U中并没有删除掉邮件C,但这不影响最后的结果)的意义上把邮件C从类别U中分离了出来。
最后再注意一下类别数的统计方法。。
171ms AC
#include <stdio.h>
#include <string.h>
#define MAXN 100000
#define MAXM 1000000
int p[MAXN+MAXM],rank[MAXN+MAXM],map[MAXN];
bool vis[MAXN+MAXM];
void scani(int &num){
char ch;
int flag=1;
while(ch=getchar(),(ch>'9'||ch<'0')&&(ch!='-'));
if(ch=='-')
flag=-1,num=0;
else
num=ch-'0';
while(ch=getchar(),(ch<='9'&&ch>='0'))//能吃掉空白字符
num=num*10+ch-'0';
num*=flag;
}
int find(int x){
int r,t,i;
r=x;
while(r!=p[r])
r=p[r];
i=x;
while(i!=r){
t=p[i];
p[i]=r;
i=t;
}
return r;
}
void merge(int ra,int rb){
if(rank[ra]==rank[rb]){
p[rb]=ra;
rank[ra]++;
}else{
if(rank[ra]>rank[rb])
p[rb]=ra;
else
p[ra]=rb;
}
}
void init(int n){
int i;
for(i=0;i<n;i++){
p[i]=map[i]=i;
rank[i]=0;
}
}
int main(){
int n,m,i,a,b,ra,rb,cnt,cas=0,end;
char ch;
while(scani(n),scani(m),n||m){
init(n);
end=n;
while(m--){
ch=getchar();
if(ch=='M'){
scani(a),scani(b);
ra=find(map[a]),rb=find(map[b]);
if(ra!=rb)
merge(ra,rb);
}else{
scani(a);
map[a]=end;
p[end]=end;
rank[end]=0;
end++;
}
}
cnt=0;
memset(vis,0,end*sizeof(bool));
for(i=0;i<n;i++){
ra=find(map[i]);
if(!vis[ra]){
cnt++;
vis[ra]=1;
}
}
printf("Case #%d: %d\n",++cas,cnt);
}
return 0;
}