题目大意:
有一堆箱子,编号是1~N,现在对这堆箱子进行如下操作;
M X Y;将X放到Y上去,并与Y变成一个整体,即下次执行M X Z;的时候,是将X和Y按原来的顺序搬到Z的上方。
C X;问X下面有多少箱子。
解题思路:
这道题其实非常有意思,当时自己没做出来,找了很多代码后才发现一个比较直观的思路。
这个思路是用并查集做的,但是得用一点逆向思维,它要求我们求出下方有多少箱子,但是求下方的箱子是比较难的!
为什么说记录下方的箱子数目比较难呢?,我们看一个例子。
假设我们要将集合1放到集合三上面的话,我们在find(4)和find(5)的时候顺便维护一个under[]数组,记录每个元素的下面的元素的数量。这一步是很容易实现的,合并也就如上图中的2,4所示。然后我们要将3,4合并到5这一步来,这个时候我们每个节点都有一个under[i]记录他下面的子节点数,但是合并之后下面的under[]肯定是不受影响,但是上方的under[]就肯定会受影响了,而且每个under[]都得加上下面那个集合里的元素的总个数,问题就出在这了,这一步就非常的难实现,为什么呢?我们知道,2,4都是被压缩了,因此我们根据子节点找父节点容易,但是要根据父节点找子节点却比较难,而且我们路径压缩的时候也只是将下面的跟新,但是恰巧下面的有不必更新。所以问题的关键就是每次都更新下边的而不更新上边的。于是将问题转换一下,用up数组记录上面的元素的个数,然后拿这个集合的总格数SetSize-up-1就能求出下边的了!
#include<stdio.h>
#define N 30009
int parent[N],SetSize[N],up[N];
void initi(){
int i;
for(i=0;i<N;i++){
parent[i]=i;
SetSize[i]=1;
up[i]=0;
}
}
int getParent(int x){
int temp;//路径压缩并更新子节点up
if(x!=parent[x]){
temp=parent[x];
parent[x]=getParent(parent[x]);
up[x]+=up[temp];
}
return parent[x];
}
void Union(int a,int b){
a=getParent(a);
b=getParent(b);
if(a==b)
return ;
parent[b]=a;
up[b]=SetSize[a];
SetSize[a]+=SetSize[b];
}
int main()
{
int n,a,b;
char opera[3];
while(scanf("%d",&n)!=EOF){
initi();
while(n--){
scanf("%s",opera);
if(opera[0]=='M'){
scanf("%d%d",&a,&b);
Union(a,b);
}else{
scanf("%d",&a);
b=getParent(a);
printf("%d\n",SetSize[b]-up[a]-1);
}
}
}
}