并查集合并已有道路并实时维护连通块数,最少新道路数就是连通块数减一。
OJ: luogu
题目 ID: P1536
难度:入门
标签:并查集图论连通块python
创建: 2026-06-20 00:23
更新: 2026-10-07 10:45
后置题目
roj 1389亲戚B 的 M 操作直接复用 A 教的「合并两个不同代表元时同步维护集合大小」这一步(A 用 size 累加、blocks-=1,B 把计数搬到根上做 sz[新根]+=sz[旧根]),只把 A 的输出「blocks-1」换成 Q 查询 sz[find(a)] 并额外叠加按大小合并与坏行 scanf 语义复刻。roj 1348城市公交网建设问题(最小生成树)B 的 Kruskal 主循环在 B 的代码里逐行指认了 A 教的这一步:find(u)、find(v) 后「两端代表元不同才合并」(father[ru]=rv,main.py 第 61-63 行),A 只教了并查集判连通与合并这一层,B 在它外面叠加按权排序、贪心选满 n-1 条边和复现 std 快排。
题意
多组数据中给出 n 个城镇和已有道路,求最少再建多少条道路,才能让任意两个城镇互相到达。输入以单独的 0 结束。
思路
已有道路把城镇分成若干连通块。若有 k 个连通块,每建一条连接不同块的道路最多使块数减少一,因此至少需要 k-1 条;把各块依次连接起来也恰好只需 k-1 条。
用并查集维护已有道路:
- 初始每个城镇单独成块,
blocks=n; - 一条道路连接两个不同代表元时合并,并令
blocks-=1; - 重边或块内道路不会改变块数;
- 输出
blocks-1。
Python 知识
- 用一个下标
pos顺序消费批量读取的整数,便于处理组数未知、以0结束的输入。 - 在
union成功时直接减少blocks,省去最后再次扫描所有节点。 answer收集每组结果,最后"\n".join(answer)一次输出。- 循环版
find配合路径减半parent[x]=parent[parent[x]],短且不依赖递归深度。 /home/rainboy/mycode/hugo-blog/content/program_language/python/oj_input_output_cheatsheet.md:多组和终止标记输入。/home/rainboy/mycode/hugo-blog/content/program_language/python/cpp_to_python_pitfalls.md:递归深度与批量输出。
代码
python
import sys
def main():
data = list(map(int, sys.stdin.buffer.read().split()))
pos = 0
answer = []
while pos < len(data):
n = data[pos]
pos += 1
if n == 0:
break
m = data[pos]
pos += 1
parent = list(range(n + 1))
size = [1] * (n + 1)
blocks = n
def find(x):
while parent[x] != x:
parent[x] = parent[parent[x]]
x = parent[x]
return x
for _ in range(m):
a, b = find(data[pos]), find(data[pos + 1])
pos += 2
if a == b:
continue
if size[a] < size[b]:
a, b = b, a
parent[b] = a
size[a] += size[b]
blocks -= 1
answer.append(str(blocks - 1))
print("\n".join(answer))
if __name__ == "__main__":
main()复杂度
一组数据有 n 个点、m 条路,时间复杂度
总结
答案只取决于已有图的连通块数。并查集合并成功时实时计数,是比“最后逐点数根”更直接的写法。