题目链接:暑前训练 前缀和 + 差分 + 快速幂 + 并查集及其应用 - Virtual Judge (vjudge.net)
这道题目的题意很简单,两个操作,一个操作是合并两个集合,另一个操作就是把某点从集合中分离,无论是合并集合也好,还是查询两个元素是否在一个集合内也好,这都是并查集的基本操作,最关键的操作就是怎样把一个点从元素中分离出去,这时候有同学可能就会说:这不很简单吗,直接将要分离的元素的父亲节点改变不就行了吗?比如说,1,3,5,6在一个集合中,集合代表元素为5,那么我们要把1,3,6三个元素分离出集合确实可以直接更改他们的父亲节点,这样是完全没问题的,因为更改这三个点完全不会影响到这个集合内的其他元素,但是我们要是需要将一个集合的代表元素分离出这个集合,还能直接修改他的父亲节点吗?就比如这个集合的6,我们修改他的父亲节点显然是没有任何意义的,这时候我们就应该这样想:要是我们要分离的元素不可能成为集合的代表元素该多好啊!这个时候我们就应该想到让这些可能被操作的元素指向一个不可能操作的虚根不就好了吗?比如说我们要操作的点的范围是1~n,那么我们一开始就让i这个点加入n+i这个集合,这样i不就不可能作为集合的代表元素了吗?我们要想删除i的话只需要让i加入一个当前不存在元素为1~n的集合不就好了吗?但是需要注意的是,我们赋初值的时候一定要赋给1~2*n+m,这又是为什么呢?首先我们使得元素为1~n的数分别加入了以n+1~2*n为代表元素的集合,其次我们要进行m次操作,每次操作都有可能是删除一个点,我们就要使这个点与一个只有代表元素且代表元素不能是1~n的空集合合并,所以我们初始化的范围就是1~2*n+m,其他就没什么难点了,下面是代码:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
const int N=2e6+10;
bool vis[N];
int fu[N];
int find(int x)
{
if(x!=fu[x]) fu[x]=find(fu[x]);
return fu[x];
}
int main()
{
int n,m;
int count=0;
while(1)
{
scanf("%d%d",&n,&m);
if(n==0&&m==0) break;
for(int i=1;i<=n;i++) fu[i]=n+i;//给每一个可能用到的节点分配一个假的父亲节点
for(int i=n+1;i<=2*n+m;i++) fu[i]=i;//让假的节点所在的集合有一个用不到的代表元素
char op[5];
int t=2*n,a,b;//保证每次分离出一个集合后,该元素指向的仍然是假根
for(int i=1;i<=m;i++)
{
scanf("%s",op);
if(op[0]=='M')
{
scanf("%d%d",&a,&b);
int f1=find(a+1),f2=find(b+1);//注意下标应该从1开始
if(f1==f2) continue;
fu[f1]=f2;
}
else
{
scanf("%d",&a);
fu[a+1]=++t;
}
}
int ans=0;
memset(vis,false,sizeof vis);
for(int i=1;i<=n;i++)
{
if(!vis[find(i)])
{
vis[find(i)]=true;
ans++;
}
}
printf("Case #%d: %d\n",++count,ans);
}
return 0;
}