并查集的删除操作中心思想:
初始化的时候,假使每个节点的root[i]=i(每个点的父节点都是它自己),然后删除某个父节点的时候,该节点可能有多个子结点,就不能保证该节点的其他节点仍然在这个集合里面。
解决方法是:将这些点的父节点指向别的数,删除该点时将该点的父节点指向集合以外的数。(说不清楚,详情见代码)
对于样例:
5 6
M 0 1
M 1 2
M 1 3
S 1
M 1 2
S 3
我们有如下的过程:
初始:
M 0 1:
M 1 2:
M 1 3:
S 1:
M 1 2:
S 3:
因为删除操作不会涉及到这些额外附加的节点,因此,我们删除的节点总是没有任何孩子节点,故保证了算法的正确性。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <algorithm>
using namespace std;
const int N = (1e+5) +100;
const int maxn = (1e+6) + 3*N ;
int root[maxn],tot;
int n,m;
void init(int n)
{
tot = 2*n;
for(int i=0;i<n;i++)
root[i]=i+n;//将0~n这些点的父节点往后移n个单位
for(int i=n;i<=tot+m;i++)
root[i]=i;//其余可能用到的点的父节点仍然指向自己
return;
}
int find(int x)//查找函数没有改变
{
int y=x;
while(y!=root[y]) y=root[y];
while(x!=root[x])
{
int px=root[x];
root[x]=y;
x=px;
}
return y;
}
void Union(int x,int y)//合并函数也不做改变
{
int fx = find(x);
int fy = find(y);
if(fx==fy) return;
root[fx]=fy;
return;
}
void Delet(int x)//删除该节点时只需要将其指向集合以外的点
{
root[x] = tot++;
return;
}
int main()
{
int cas=0;
while(~scanf("%d%d",&n,&m))
{
if(n==0 && m==0) break;
init(n);
while(m--)
{
char op;
int x,y;
scanf(" %c",&op);
if(op=='M') scanf("%d%d",&x,&y), Union(x,y);
else scanf("%d",&x),Delet(x);
}
int ans=0;
bool vis[maxn];
memset(vis,true,sizeof(vis));
for(int i=0;i<n;i++)
if(vis[find(i)]) ans++,vis[find(i)]=false;
printf("Case #%d: %d\n",++cas,ans);
}
return 0;
}