题意:一个个数在30,000以内的盘子,编号从1开始,之后有K(K < 100,000)次操作;
*M a b In a move operation, Farmer John asks Bessie to move the stack containing cube X on top of the stack containing cube Y.
*C a In a count operation, Farmer John asks Bessie to count the
number of cubes on the stack with cube X that are under the cube X and
report that value.
Sample Input
6 (P) M 1 6 C 1 M 2 4 M 2 6 C 3 C 4
Sample Output
1 0 2
注:不会输入盘子的个数n,直接输入操作数P;(初始化)
思路:f[]就可以容易的压缩出根节点,同时前面几题知道,对于一个子节点到根节点之间的关系可以通过路径压缩时建立在原父节点的基础之上;但是父节点不能得到子节点的信息;
回归到本题,对于两个盘子集合的合并,按照上面,可以知道如果设数组num[i]表示盘子i下面盘子的个数,并且把最下面的盘子当成根节点,那么对于查询C a答案就是路径压缩之后的num[a];
但是怎么合并呢?按照偏移向量的思想(先路径压缩,再由子节点得到被合并的根节点与最终根节点之间的关系,因为这样就可以把原根节点就是根节点的子节点,实现了两颗树的合并),那么在fu与fv之间就需要更新之间的关系,即f[fu] = fv时,需要得到合并之后fu下面有多少个盘子;
这就需要知道根节点树有多少个节点;直接使用rk[]在合并的时候,更新一下根节点之间的关系(num[])即可;
ps:要清楚在合并集合时需要合并的数据有什么?
// 1064K 579MS #include<iostream> #include<cstdio> using namespace std; #define rep0(i,l,r) for(int i = (l);i < (r);i++) #define rep1(i,l,r) for(int i = (l);i <= (r);i++) #define rep_0(i,r,l) for(int i = (r);i > (l);i--) #define rep_1(i,r,l) for(int i = (r);i >= (l);i--) #define MS0(a) memset(a,0,sizeof(a)) #define MS1(a) memset(a,-1,sizeof(a)) const int MAXN = 30030; int f[MAXN]; int num[MAXN],rk[MAXN];//num[i]:盘子i下面有多少个盘子,rk[]表示根节点所在树有多少个节点 int Find(int a)//** { if(a == f[a]) return f[a]; int fa = Find(f[a]); num[a] += num[f[a]];//加上没压缩之前的fa,此时num[f[a]]已经递推到了要fa的距离 return f[a] = fa; } bool _union(int u,int v) { int fu = Find(u),fv = Find(v); f[fu] = fv; num[fu] = rk[fv];//** rk[fv] += rk[fu]; return false; } int main() { int p,T,kase = 1; scanf("%d",&p); rep1(i,0,MAXN) rk[i] = 1,num[i] = 0,f[i]= i; rep0(i,0,p){ char op[2];int a,b; scanf("%s",op); if(op[0] == 'M'){ scanf("%d%d",&a,&b); _union(a,b); } else{ scanf("%d",&a); Find(a); printf("%d ",num[a]); } } return 0; }