题意:有n个集合,m个操作。对每个操作输入的如果为M,那就合并两个集合,否则将此点分离出去,问一共有多少个集合。
举例:对于
M 0 2
M 1 2
S 2
合并后的集合是{0,1,2},把2删除掉之后, 集合变为 {0,1}
思路:建立一个映射,通过改变将删除点的映射关系,同时改变其父节点到一个新的、虚设的点。这种方法很费空间。
直接将一个点的父节点改成自身是不行的,因为如果这点已经是一个集合的根节点,那它的父节点本来就等于自身。
#include <cstdio>
#include <cstring>
const int N=1100005;
class Disjoint_Set
{
public:
int father[N]; /*father[x]表示x的父节点*/
int rank[N]; //以该节点为根节点的点数,同时起到按秩合并的作用
int mapping[N]; //节点映射数组
int id;
void Init (int n)
{//本题对应元素为0~n-1
for (int i=0;i<n;i++)
Make_Set (i);
id=n; //////
}
void Make_Set (int x)
{
father[x]=x;
mapping[x]=x;
rank[x]=1;
}
int Find_Set (int x)
{
if (x != father[x])
father[x] = Find_Set(father[x]);//回溯
return father[x];
}
void Union (int x,int y)
{
int a=Find_Set (x);
int b=Find_Set (y);
if (a == b)
return;
if (rank[a] >= rank[b])
{
father[b]=a;
rank[a]+=rank[b];
}
else
{
father[a]=b;
rank[b]+=rank[a];
}
}
void Delete (int x)
{//改变待删除点的映射数组
father[id]=id;
mapping[x]=id++;
}
}ob;
bool visit[N];
int main ()
{
int n,m,Cas=1;
while (scanf("%d%d",&n,&m),n||m)
{
ob.Init(n);
char str[4];
int a,b;
while (m--)
{
scanf("%s%d",str,&a);
if (str[0]=='M')
{
scanf("%d",&b); //利用映射值
ob.Union(ob.mapping[a],ob.mapping[b]);
}
else
ob.Delete(a);
}
memset(visit,false,sizeof(visit));
int ans=0;
for (int i=0;i<n;i++)
{
a=ob.Find_Set(ob.mapping[i]);
if (visit[a]==false)
{
ans++;
visit[a]=true;
}
}
printf("Case #%d: %d\n",Cas++,ans);
}
return 0;
}