时间限制 \(1s\) | 空间限制 \(125M\)
约翰和贝茜在玩一个方块游戏。编号为 \(1\ldots n\) 的 \(n ( 1 \leq n \leq 30000 )\)个方块正放在地上,每个构成一个立方柱。
游戏开始后,约翰会给贝茜发出 \(P ( 1 \leq P \leq 100000 )\) 个指令。指令有两种:
写个程序帮贝茜完成游戏。
数据范围:\(1 \leq n \leq 30000; \space 1 \leq P \leq 100000\)
看到将立方柱移动到另外一个立方柱上的操作,我们可以想到使用并查集维护。但是,我们又该如何维护某个方块下面的方块数量呢?我们可以尝试使用边带权的并查集完成。
边带权的并查集的特点就是每一条边都有相应的权值,最终往往是要求求出某一个节点到它所在根节点的权值之和。
在这道题中,我们定义每一条边的权值为 \(1\),那么答案就是某个节点到它所在根节点的权值之和。
我们如何维护这个东西呢?一个看似可行的方法是在查询的时候每经过一个元素加上 \(1\)。
但这个方法的问题很明显:我们在进行路径压缩之后某个节点到根节点的距离不一定是 \(1\),而且我们也没法进行合并。但这个方法也不是不可取,我们根据问题稍微修改一下即可。
我们在每一个节点处记录一个权值,这个权值代表的是当前元素与其父节点之间的路径的权值。这样,在路径压缩的时候,我们可以将当前节点的权值加上其父节点的权值。由于到当前节点的时候,其父节点的父节点一定是根节点,所以此时父节点的权值就是父节点到根节点的权值和,当前节点到根节点的权值和就是当前节点的权值加上父节点的权值。
我们再考虑一下合并时的更改。对于每一个集合,我们再记录一个值:集合的大小。如果我们将某个立方柱放到另一个立方柱上,那么叠在上面的那个立方柱的根节点的父节点就是下面的立方柱的根节点,此时下面的立方柱的所有方块都在上面立方柱的根节点下面。所以我们直接将上面立方柱的权值改成下面立方柱的大小,再将整体的根节点改为下面立方体的根节点并更新整体的大小即可。
#include <iostream> #include <cstdio> using namespace std; const int N = 3e4+10, P = 1e5+10; int p, x, y, fx, fy, father; int fa[N], val[N], siz[N]; char opt; int find(int x) { if (x == fa[x]) return x; father = find(fa[x]); val[x] += val[fa[x]]; fa[x] = father; return fa[x]; } int read() { int x = 0, f = 1; char c = getchar(); while (c < '0' || c > '9') { if (c == '-') f = -1; c = getchar(); } while (c >= '0' && c <= '9') { x = (x << 1) + (x << 3) + (c ^ 48); c = getchar(); } return x * f; } int main() { p = read(); for (int i = 1; i <= 3e4; i++) { fa[i] = i; siz[i] = 1; } for (int i = 1; i <= p; i++) { cin >> opt; if (opt == 'M') { x = read(); y = read(); fx = find(x); fy = find(y); fa[fx] = fy; val[fx] = siz[fy]; siz[fy] += siz[fx]; } else { x = read(); fx = find(x); printf("%d\n", val[x]); } } return 0; }