带集合大小的并查集:M 合并时把小家族挂到大家族根下并把人数累加到新根,Q 输出 sz[find(a)],均摊近似线性。
OJ: roj
题目 ID: 1389
难度:普及-
标签:并查集连通块等价类python
创建: 2026-09-30 08:29
更新: 2026-10-13 22:33
题目描述
M a b:与 是亲戚,合并两个家族; Q a:输出所在家族的人数。
思路
并查集维护每个家族的根,根上记录 sz(家族人数)。合并时按大小合并,小家族挂到大家族根下并累加人数;查询时先 find(a) 找到根,再输出 sz[根](sz 只在根上有效)。路径压缩保证均摊近似常数。
参考代码
Python 版:
python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-09-30 08:29
# update_at: 2026-10-13 22:28
import sys
class DSU:
"""并查集 (Disjoint Set Union)"""
def __init__(self, n: int):
# 1-indexed, fa 初始指向自己,sz 初始为 1
self.fa = list(range(n + 1))
self.sz = [1] * (n + 1)
def find(self, x: int) -> int:
"""查找根节点,两趟路径压缩:先走到根,再把整条路径挂到根上"""
root = x
while self.fa[root] != root:
root = self.fa[root]
while self.fa[x] != root:
self.fa[x], x = root, self.fa[x]
return root
def same(self, x: int, y: int) -> bool:
"""判断 x 和 y 是否属于同一个集合"""
return self.find(x) == self.find(y)
def unite(self, x: int, y: int) -> bool:
"""按大小合并两个集合 (小树挂大树);已在同一集合返回 False"""
fx, fy = self.find(x), self.find(y)
if fx == fy:
return False
if self.sz[fx] < self.sz[fy]:
fx, fy = fy, fx
self.fa[fy] = fx
self.sz[fx] += self.sz[fy]
return True
def solve() -> None:
"""M a b 合并两个家族,Q a 输出 a 所在家族的人数。"""
# token 里整数与操作符 M/Q 混排,没法整体 map(int),按位置顺序消费、用到时再转 int
data = iter(sys.stdin.buffer.read().split())
n, m = int(next(data)), int(next(data))
dsu = DSU(n) # sz 只在根上有效,取人数要先 find 到根
out: list[str] = []
for _ in range(m):
op = next(data) # 操作符 token:b"M" 合并 / b"Q" 查询
if op == b"M":
a, b = int(next(data)), int(next(data))
dsu.unite(a, b) # 按大小合并,小家族整棵挂到大家族的根下
elif op == b"Q":
a = int(next(data))
out.append(str(dsu.sz[dsu.find(a)]))
print("\n".join(out))
if __name__ == "__main__":
solve()C++ 版(同一算法):
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-05 12:30
* update_at: 2026-10-13 22:33
*/
#include <iostream>
using namespace std;
typedef long long ll;
// 并查集竞赛精简版(不用 struct):路径压缩 + 按大小合并
// 口诀:find 找根压到底,unite 小树挂大树
// 易错点:init 别只写 fa[i] = i,sz[i] 也要置 1;合并后 sz 只在新根上有效
// 和别的模板一起用时 maxn 只留一份
const int maxn = 100005;
int fa[maxn], sz[maxn]; // sz[x] 仅在根上有效
void init(int n) {
for (int i = 1; i <= n; i++) fa[i] = i, sz[i] = 1;
}
// 查 x 的根,顺手把整条链压到根上
int find(int x) {
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
bool same(int x, int y) { return find(x) == find(y); }
// 小树挂大树,返回是否真的发生了合并
bool unite(int x, int y) {
int fx = find(x), fy = find(y);
if (fx == fy) return false;
if (sz[fx] < sz[fy]) swap(fx, fy);
fa[fy] = fx;
sz[fx] += sz[fy];
return true;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
init(n);
for (int i = 0; i < m; ++i) {
char op;
cin >> op;
if (op == 'M') {
int a, b;
cin >> a >> b;
unite(a, b); // 按大小合并,小家族整棵挂到大家族的根下
} else if (op == 'Q') {
int a;
cin >> a;
cout << sz[find(a)] << '\n'; // sz 只在根上有效,必须先 find 再取 sz
}
}
return 0;
}