题目大意:给定编号为1到30000的小块。可以进行合并和查询两种操作
- 合并:将含有x的整块摞到含有y的整块上面,合并成一个新的整块
- 查询:输出x下方的块的数量
一眼并查集,但就是不知道怎么写。想了很久才想到要以每个整块的底块作为并查集的根,并维护某个块底下的小块的数量作为并查集的权值。另开一个并查集,以每个整块的顶块作为根。
每次合并,都将x所在整块的底块的权值赋值为y所在整块的小块总数。不难知道,y所在的整块的小块总数就是y的顶块下面的小块的数量,即带权并查集中y顶块的权值+1。
在带权并查集的查找合并二合一(ffind)的过程中,对于每一个点,如果它的f不为本身,就把它的权值累加上它此时的f的权值。写的时候我对带权并查集并不算非常熟悉,觉得这么写会导致重复查询某一点时重复累加,但其实不会。因为根的权值永远是0。一个点累加过一次之后,这个点就会指向根节点,所以重复累加也是加的0。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <map>
#include <set>
#include <queue>
#include <stack>
#include <cctype>
using namespace std;
int f[30006], sum[30006], fd[30006];
int ffind(int x) //这基本是标准的带权并查集ffind
{
if (x == f[x])
return f[x];
int t = f[x];
f[x] = ffind(t);
sum[x] += sum[t];
return f[x];
}
int dfind(int x)
{
if (x == fd[x])
return x;
return fd[x] = dfind(fd[x]);
}
void mge(int x, int y)
{
x = ffind(x);
y = ffind(y);
if (x == y)
return;
f[x] = y;
y = dfind(y);
ffind(y); //y下面不一定合并好了,权值不一定对,所以要加这一行。这里卡了很久
sum[x] = sum[y] + 1;
fd[y] = x;
}
int main()
{
char op[5];
int a, b, _, i;
for (i = 0; i <= 30000; i++)
f[i] = fd[i] = i;
scanf("%d", &_);
while (_--)
{
scanf("%s%d", op, &a);
if (op[0] == 'C')
{
ffind(a); //因为a的下面不一定已经合并好了,所以要加这一步
printf("%d\n", sum[a]);
continue;
}
scanf("%d", &b);
mge(a, b);
}
return 0;
}