符号约定
下文固定一个正整数 n,定义全集 U={1,2,…,n}。
并查集用一个父亲函数 p:U→U 表示,初始化 ∀x∈U,p(x)=x。
称一个父亲函数 p 是合法的,当且仅当对任意 x∈U,不断应用 p:x, p(x), p(p(x)),… 最终一定会到达某个点 r,并且这个点满足 r=p(r),这个 r 就叫做 x 在父亲函数 p 下的根,记为 rootp(x)。
一个集合划分 P 指 U 的若干个非空子集,它们两两不交,且并集为 U。
对合法的 p,划分 Pp 定义为 \{ \[r\] \mid r\in U,r=p(r)\},其中 \[r\]=\{x\in U\mid p(x) = r\}
路径压缩
求 rootp(x) 的函数 find(x) 的操作过程如下:
- 若 p(x)=x,返回 x;
- 若 p(x)=x,先递归求出 r=find(p(x)),再令 p(x)←r,并返回 r。
假设 x 到根路径为 v1,v2,…,vk,其中 vk 是根,那么本次操作就是 ∀i, 1≤i<k, p(vi)←vk。
下证设进行操作前父亲函数 p 是合法的,那么进行一次操作之后父亲函数 p′ 也是合法的,并且 ∀y∈U, rootp′(y)=rootp(y)。
证明:
任取一个 y∈U:
- 如果从 y 出发不断应用 p 的过程中没有经过 v1,v2,…,vk−1,那么 y 在 p 下走过的路径与在 p′ 下相同,因此仍到达同一个根;
- 否则设从 y 出发在 p 下第一次经过的被修改点是 vi,则在 p′ 下到达 vi 后下一步直接到达 vk,此时 rootp′(y)=rootp(y)=vk,并且 p(vk)=p′(vk)=vk。
集合合并
合并 x,y 所在集合的函数 merge(x,y) 的操作过程如下:
- 求 rx=find(x) 且 ry=find(y);
- 若 rx=ry,不做更改,否则令 p(rx) 修改为 ry。
设执行了两次 find 之后,修改 p(rx) 为 ry 前的父亲函数是 p,修改后的父亲函数是 p′,那么
-
满足 rootp(u)=rx 的 u 满足 rootp′(u)=ry;
-
满足 rootp(u)=ry 的 u 满足 rootp′(u)=ry;
-
满足 rootp(u)∈/{rx,ry} 的 u 满足 rootp′(u)=rootp(u)。
若 rx=ry,则 p′=p,显然合法;否则 rx,ry 是两个不同的根,令 rx 指向 ry 后所有点都能找到一个根,所以 p′ 仍然是合法的。
DSU-NEXT
下面介绍一个常用的 trick,用来处理对一个固定的 n,每次处理 l∼r 中的所有点,但是每一个点只需要被处理一次,如将初始全 0 的序列区间修改为 1。
定义带哨兵的全集 I={1,2,…,n+1},其中 n+1 永远不会被删除,维护一个存活集合 S⊆I,初始为 S=I。
支持对这个集合 S 进行如下两种操作:
- 对 x∈S,x=n+1,令 S←S∖{x}
- 在 S 中查询 ≥i 的最小的数,记为 N(i),这里不要求 i∈S,只要求 i∈I。
因为 n+1∈S 永远成立,所以 N(i) 总是良定的。
我们可以用并查集维护这个集合,具体地讲,初始时 ∀x∈I,p(x)=x,那么:
- 删除操作实现起来即 p(x)←rootp(x+1);
- 查询操作就是返回 rootp(i)。
我们来证明任意时刻都有 rootp(i)=N(i)。
对删除次数进行归纳,没删除时显然成立,假设某次删除前集合为 S,删除了 x,分 x−1,x+1 分别是否存活四种情况画个图讨论即可。