并查集、MST 与离线激活 · HTML
数学 / 逻辑形态
边或点带 key;查询只依赖 key 与阈值 t 的比较。排序事件与查询,使可用集合单向增长。
识别信号
- 许多不同阈值下的连通查询
- 越过阈值后对象永久可用
- 查询允许离线重排
- 激活只引起邻近组件合并
- 逐查询 BFS 会重复扫描
容易误判的相似信号
| Signal | Why it is different |
|---|
| 必须实时按到达顺序回答 | 离线排序改变处理次序,真正在线问题需其他结构。 |
| 状态既加入又删除 | 普通 DSU 适合单向激活;非单调变化需 rollback 或分治结构。 |
核心不变量
回答阈值 t 前,恰好所有满足条件的事件已激活,DSU 组件等于该阈值下真实连通块。
状态、转移与处理顺序
| Item | Definition |
|---|
| State | 排序事件、带原下标查询、DSU、扫描指针,以及点模型中的 active 标志。 |
| Transition | 阈值前进时激活所有新事件并 union;随后读取查询并按原下标回填。 |
| Frontier / order | 扫描指针左侧全部激活,右侧全部未激活,指针只向右。 |
Python 骨架
def offline_connectivity(n, edges, queries):
p = list(range(n))
def find(x):
while x != p[x]:
p[x] = p[p[x]]; x = p[x]
return x
def union(a, b):
a, b = find(a), find(b)
if a != b: p[b] = a
edges = sorted(edges) # (weight,u,v)
order = sorted((lim, i, u, v) for i, (lim,u,v) in enumerate(queries))
ans, j = [False] * len(queries), 0
for lim, i, u, v in order:
while j < len(edges) and edges[j][0] <= lim:
_, a, b = edges[j]; union(a, b); j += 1
ans[i] = find(u) == find(v)
return ans
正确性思路
- 排序保证扫描到 lim 时,所有且仅有 key<=lim 的事件位于指针左侧。
- 每个激活边执行 union,所以 DSU 与激活子图组件一致。
- 查询只读取当前状态,得到原阈值定义下的连通性。
- 按原下标回填使重排不改变输出顺序。
复杂度
| Time | Space | Parameters |
|---|
| O((E+Q) log(E+Q)+(E+Q)α(V)) | O(V+E+Q) | V 对象数,E 事件数,Q 查询数 |
边界条件
- 严格小于与小于等于影响同值顺序
- 无事件时仍要处理自连通
- 阈值在所有 key 之外
- 相同 key 事件需成批处理
- 重复查询仍要保留各自下标
错误模式
| Pattern | Why it fails / fix |
|---|
| 同值 tie-breaking 写反 | <= 条件下同值事件必须先于查询。 |
| 按排序顺序 append | 必须按原查询下标回填答案。 |
| 每个查询重建 DSU | 会失去共享扫描的复杂度优势。 |
选择与排除规则
- 查询可重排且可用集合单调扩张:选离线激活。
- 只有单查询时,直接搜索可能更简单。
- 先明确 < 还是 <=,再定事件顺序。
- 点激活时只与已激活邻居 union。
相关模板
| Relation | Template | Boundary |
|---|
| 动态摘要 | 并查集动态连通性 | 激活后维护组件 |
原创微型例子
| 环节 | 内容 |
|---|
| Result | 阈值 4 只含第一边;阈值 5 先加入两条同值边再查询。 |
| Scenario | 边在阈值 2、5、5 激活,查询阈值 4 与 5 的连通性。 |
| Walkthrough | 指针只右移,每条边只处理一次,两个查询共享此前工作。 |
Recall prompts
我的补充