HDU 2818 Building Block
带权并查集。
有N
个块,刚开始,每个块自成一堆,下面进行P
次操作或查询,操作是指把包含(编号为X
的块)的堆整体摞到包含(编号为Y
的块)的堆上;查询是指给定某个块编号,问你这个块下面压着几个块。因为在不停变化,这道题需要输入一个查询后立即输出一个结果。
有点类似HDU 3635,但不一样。
画个图来说,圆点代表某个点,方块点代表某个子树。点右边的数字代表同层中被连接到父节点的次序。cnt[]
表示当前结点下面压着几个点(确切地说,刚刚被查询过的点的cnt[]
值表示此时它下面压着几个块;否则,可能表示前者,也可能表示这个点被连接到它现在的父节点的那个时候它下面压着几个块(第二种说法是万金油))。
这个图想说明什么呢?首先,每个堆在被连接到另一个堆时,一定被连接到这另一个堆的根节点(这就是并查集的基本操作)。所以,就上图而言,4
再被连接到3
时,当前的3
一定是根节点,所以这时的cnt[4]
只能包含3
和3
的其他子树;而3
的其他子树分为两种,一种比4
更早连接到3
(比如C
),一种更晚(比如F
),显然只能计算更早的。
其次,在某个结点从下向上找根的过程中,在左边的柱形图中一定是向下移动的。右边的图中某个点每向上找一个父节点,在左边的图中就向下跨越一部分块,而且跨越的这些块的数量一定等于这个点的cnt[]
值。所以可以看出,每个点下面压着的块其实是分层的,每一层都和这个点的每一个祖先结点有关。
说了这么多。。就这样吧,再说就没意思了。
#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;
const int MAXN = 30005;
int P;
int pre[MAXN];
int cnt[MAXN];
int f(int x)
{
if (pre[x] < 0) return x;
int t = f(pre[x]);
cnt[x] = cnt[x] + cnt[pre[x]];
return pre[x] = t;
}
void u(int a, int b)
{
int f1 = f(a);
int f2 = f(b);
if (f1 == f2) return;
cnt[f1] = -pre[f2]; // 固定的合并方向
pre[f2] += pre[f1];
pre[f1] = f2;
}
int main()
{
memset(pre, -1, sizeof pre);
memset(cnt, 0, sizeof cnt);
char c;
int a, b;
scanf("%d", &P);
for (; P--;)
{
getchar();
scanf("%c", &c);
if (c == 'M')
{
scanf("%d%d", &a, &b);
u(a, b);
}
else
{
scanf("%d", &a);
f(a);
printf("%d\n", cnt[a]);
}
}
return 0;
}