题意:对n份邮件进行m次操作,操作分为两种:(1)M X Y:表示将右键X和邮件Y归为同一类邮件;(2)S X:表示将邮件X从X所属的一类邮件中取出(即X单独属于一类邮件)。 问m次操作后总的邮件种类是多少。
分析:常规的并查集是无法进行删除操作的,因为一旦根节点发生变化,其子节点的根节点信息就会丢失,于是我们需要将其变形,设置虚拟节点。首先将0~n-1的根节点设为n~2n-1,将2n~2n+m-1作为备用节点。删除操作时将x的根节点置为备用节点,然而实际上x的节点信息之前已经映射到x+n中,那么x+n的兄弟节点的根节点信息依然保留了下来。最后邮件种类的个数就等于0~n-1的根节点的种类,可以用set来记录统计个数。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<set>
using namespace std;
const int maxn=1e5+5;
const int maxm=1e6+5;
int n,m;
int pre[maxn*2+maxm];
int cnt;
inline void init()//初始化操作
{
for(int i=0;i<n;i++)
pre[i]=i+n;
for(int i=n;i<n+n+m;i++)
pre[i]=i;
}
inline int root(int x)//找根操作
{
return pre[x]==x?x:pre[x]=root(pre[x]);
}
inline void merge(int x,int y)//合并操作
{
int a=root(x);
int b=root(y);
if(a!=b)
pre[a]=b;
}
inline void erase(int x)//删除操作
{
pre[x]=cnt++;
}
int main()
{
int T=1;
while(~scanf("%d%d",&n,&m)&&(n+m))
{
char c;
int x,y;
init();
cnt=n+n;
while(m--)
{
getchar();
scanf("%c",&c);
if(c=='M')
{
scanf("%d%d",&x,&y);
merge(x,y);
}
else
{
scanf("%d",&x);
erase(x);
}
}
set<int>s;
for(int i=0;i<n;i++)
s.insert(root(i));
printf("Case #%d: %d\n",T++,s.size());
}
return 0;
}