http://acm.hdu.edu.cn/showproblem.php?pid=2473
写挫了,wa了一次
题意简单来说,就是给你n个点,m条命令,M a b表示a b在同一个集合,S a表示从a的集合把a拿走,当做一个新的集合。问最后有几个集合。
这样的题意已经抽象好了,提到集合,必然是并查集,没什么好说的,但是我们的并查集都是添加点,没有删除的,今天遇到这么一个题,需要把并查集修改一下。
想法是:
比如有4个点0 1 2 3,那么我加4个点4 5 6 7,1->4 ,2->5 , 3->6 ,4->7,如果1和2连接,就用1的祖先指向2的祖先,要删除一个点的时候,比如3,直接fa【3】=某个数,某个数是多少呢,最坏的情况是m条命令都是删除,那么我再加m个点,比如m=3,那么就加上8 9 10这三个点,3->8,这样做,所有的(0,n-1)中的点都是叶子,不会因为某个点删除了,从而指向它的点跟着过去,比如2->3,不会有删除3同时把2带走的效果,因为所有的祖先都是不在(0,n-1)的,所有的节点的父亲都不在(0,n-1)里面,所以你可以随便删除,都不会影响到其他的点。
最后如何统计集合数?很简单,建一个set,把(0,n-1)所有的祖先够扔到set里,最后set的size就是集合的数量。
提一句,找父亲结点的时候用到路径压缩。见代码:
#include "iostream"
#include "cstdio"
#include "cstring"
#include "cmath"
#include "stdlib.h"
#include "algorithm"
#include "set"
using namespace std;
int fa[1200010];
int n,m,a,b,cnt,cas=1;
char op[10];
set<int> st;
int getfa(int x)
{
if(fa[x]==x)
return x;
fa[x]=getfa(fa[x]);
return fa[x];
}
int main()
{
//freopen("in.txt","r",stdin);
while(scanf("%d %d",&n,&m))
{
if((m|n)==0)
break;
cnt=2*n;
for(int i=0; i<n; i++)
fa[i]=i+n;
for(int i=n; i<2*n+m; i++)
fa[i]=i;
for(int i=0; i<m; i++)
{
scanf("%s",op);
if(op[0]=='M')
{
scanf("%d %d",&a,&b);
fa[getfa(a)]=getfa(b);
}
else
{
scanf("%d",&a);
fa[a]=cnt++;
}
}
st.clear();
for(int i=0; i<n; i++)
st.insert(getfa(i));
printf("Case #%d: %d\n",cas++,st.size());
}
return 0;
}
水平有限,众神轻喷。