【Code】可追溯化数据结构


0.可追溯化

对于数据结构 D D D,初始状态为 D 0 D_{0} D0,支持操作 U 1 , U 2 , . . . U s U_{1},U_{2},...U_{s} U1,U2,...Us和询问 Q 1 , Q 2 , . . . , Q t Q_{1},Q_{2},...,Q_{t} Q1,Q2,...,Qt

前者可以理解为该数据结构到自身的映射,后者可以理解为该数据结构到所需类型的映射

(另外,可能操作和询问都还有额外的自变量,即是一个多元的函数)

部分可追溯化:

对上述数据结构 D D D的部分可追溯化,即维护操作序列 a i a_{i} ai,并支持:

1.在 a i a_{i} ai中插入一个元素 x x x或删除一个元素(其中 1 ≤ x ≤ s 1\le x\le s 1xs

2.设当前序列长度为 L L L,求 Q x ( U a L ( . . . U a 2 ( U a 1 ( D 0 ) ) . . . ) ) Q_{x}(U_{a_{L}}(...U_{a_{2}}(U_{a_{1}}(D_{0}))...)) Qx(UaL(...Ua2(Ua1(D0))...))(其中 1 ≤ x ≤ t 1\le x\le t 1xt

完全可追溯化:

对上述数据结构 D D D的完全可追溯化,即维护操作序列 a i a_{i} ai,并支持:

1.在 a i a_{i} ai中插入一个元素 x x x或删除一个元素(其中 1 ≤ x ≤ s 1\le x\le s 1xs

2.设当前序列长度为 L L L,求 Q x ( U a y ( . . . U a 2 ( U a 1 ( D 0 ) ) . . . ) ) Q_{x}(U_{a_{y}}(...U_{a_{2}}(U_{a_{1}}(D_{0}))...)) Qx(Uay(...Ua2(Ua1(D0))...))(其中 1 ≤ x ≤ t , 1 ≤ y ≤ L 1\le x\le t,1\le y\le L 1xt,1yL

(为了方便,以下均用 n n n表示这两种操作的次数和)

常规做法:

通过"部分可追溯化",可以得到一个"完全可追溯化"的常规做法

具体的,对操作序列( a i a_{i} ai)分块,每一个块按照"部分可追溯化"的做法处理,对块外的部分暴力,通常可以做到 n ⋅ T ( n ) \sqrt{n}\cdot T(n) n T(n)的复杂度(其中 T ( n ) T(n) T(n)为"部分可追溯化"的复杂度)


1.并查集

对于初始边集为空的无向图,支持:加入一条边、查询两点连通性

部分可追溯化:

如果将其可追溯化,删除操作即等价于删边操作

另外,加边操作是满足交换律的,因此即变为经典的动态图连通性问题

如果允许离线,可以使用线段树分治+按秩合并并查集或LCT维护删除时间最大生成树,单次操作时间复杂度分别为 o ( log ⁡ 2 n ) o(\log^{2}n) o(log2n) o ( log ⁡ n ) o(\log n) o(logn)

如果强制在线,可能需要一些更高级的数据结构

完全可追溯化:

可以使用线段树分治,并用LCT代替按秩合并并查集,维护两点之间位置最后的边即可,单次操作时间复杂度也为 o ( log ⁡ 2 n ) o(\log^{2}n) o(log2n)


2.队列

对于初始为空的序列,支持:在后端加入一个元素、删除前端的元素、查询前端或后端的元素

(后两个操作和询问时保证序列非空)

部分可追溯化:

注意到删除操作同样是满足交换律的,因此只需要统计删除操作的次数即可

在此基础上,即维护一个序列,支持加入和删除一个元素,并查询某个位置的元素

另外,注意到查询的位置是连续移动的,因此可以使用链表维护,单次操作时间复杂度为 o ( 1 ) o(1) o(1)

完全可追溯化:

可以使用平衡树来维护操作序列(统计删除操作的个数)和该序列,单次操作时间复杂度为 o ( log ⁡ n ) o(\log n) o(logn)


3.栈

对于初始为空的序列,支持:在后端加入一个元素、删除后端的元素、查询后端的元素

(后两个操作和询问时保证序列非空)

可追溯化:

将插入操作看作1,删除操作看作-1,问题即查询操作序列中最后的位置满足其之后元素和大于0的位置上插入的值(显然该位置上必然是插入操作)

可以使用平衡树维护操作序列,单次操作时间复杂度为 o ( log ⁡ n ) o(\log n) o(logn)


4.双端队列

对于初始为空的序列,支持:在前端或后端加入一个元素、删除前端或后端的元素、查询前端或后端的元素

(后两个操作和询问时保证序列非空)

可追溯化:

以查询前端的元素为例,结果有两种情况:

1.该元素是在前端加入的,那么只需要考虑前端的操作即可,做法与栈相同

2.该元素是在后端加入的,来考虑某次(后端)加入的元素成为答案的条件:(1)其之前队列中所有元素都被删除;(2)其本身不被右端删除

更具体的来说,将右端加入操作看作1,右端删除操作看作-1,即要求该后缀和恰为剩余元素个数(加入操作数-删除操作数)且其不存在更短的后缀使得其(指较短的)后缀和大于等于其(指该元素)

将其变为查询最后的位置满足其之后的元素和大于等于剩余元素,再检验其是否恰为剩余元素个数即可

对两者分别查询,如果两者都存在那么是第二种情况(这是因为第一种的判定并不完善)

可以使用平衡树维护操作序列,单次操作时间复杂度为 o ( log ⁡ n ) o(\log n) o(logn)


5.优先队列

对于一个初始为空的集合,支持:加入一个元素、删除最小的元素、查询最小的元素

(保证第一个操作加入的元素互不相同,后两个操作和询问时保证集合非空)

部分可追溯化:

i i i次操作(指 U a i ( . . . U a 2 ( U a 1 ( D 0 ) ) . . . ) U_{a_{i}}(...U_{a_{2}}(U_{a_{1}}(D_{0}))...) Uai(...Ua2(Ua1(D0))...))后的集合为 S i S_{i} Si,问题即求 S L S_{L} SL

为了方便,约定 S 0 = ∅ S_{0}=\empty S0=、第 t t t次操作前指 [ 1 , t ] [1,t] [1,t]中的 t t t次操作、第 t t t次操作后指 ( t , L ] (t,L] (t,L]中的 L − t L-t Lt次操作

定义 t t t是一个桥当且仅当 S t ⊆ S L S_{t}\subseteq S_{L} StSL(显然0和 L L L均是桥),显然若 t t t是一个桥,具有以下两个性质:

1.第 t t t次操作前删除且在 S L S_{L} SL中的元素等于第 t t t次操作前加入且在 S L S_{L} SL中的元素(所构成的集合)

2.第 t t t次操作后删除且不在 S L S_{L} SL中的元素等于第 t t t次操作后加入且不在 S L S_{L} SL中的元素(所构成的集合)

直接维护 S L S_{L} SL,那么考虑某次操作(指对 a i a_{i} ai)对 S L S_{L} SL的影响:

1.在第 t t t次操作后加入"加入一个元素"的操作,那么对于 S L S_{L} SL而言加入的即第 t t t次操作后删除的最大值(注意还要对加入的元素再取较大值)

t l = max ⁡ x ≤ t , x 为 桥 x t_{l}=\max_{x\le t,x为桥}x tl=maxxt,xx,考虑第 t ′ t' t次操作删除的元素(其中 t l < t ′ ≤ t t_{l}<t'\le t tl<tt),显然 S t ′ ⊈ S L S_{t'}\not\subseteq S_{L} StSL(否则 t ′ t' t也为桥),因此总存在 x ∈ S t ′ x\in S_{t'} xSt x ∉ S L x\not\in S_{L} xSL,那么 x x x必然在之后被删除且显然 x > x> x>该元素

换言之,不妨改为求第 t l t_{l} tl次操作后删除的最大值,同时注意到其保证不会重复加入一个元素,那么也可以理解为第 t l t_{l} tl次操作后删除且不在 S L S_{L} SL中的最大值,这也等于第 t l t_{l} tl次操作后加入的且不在 S L S_{L} SL中的最大值

2.在第 t t t次操作后加入"删除最小的元素"的操作,令 t r = min ⁡ x > t , x 为 桥 x t_{r}=\min_{x>t,x为桥}x tr=minx>t,xx,类似的分析不难得到在 S L S_{L} SL中所删除的元素即第 t r t_{r} tr次操作前加入的且在 S L S_{L} SL中的最小值

3.删除第 t t t次操作且第 t t t次操作为"加入一个元素"的操作,若第 t t t次操作加入的元素在 S L S_{L} SL中则删除该元素,否则删除第 t r t_{r} tr次操作前加入的且在 S L S_{L} SL中的最小值

4.删除第 t t t次操作且第 t t t次操作为"删除最小的元素"的操作,加入第 t l t_{l} tl次操作后加入的且不在 S L S_{L} SL中的最大值

由此,即从删除元素的最大值变成了加入元素的最大值,显然后者更容易维护

更具体的,将加入 x ∈ S L x\in S_{L} xSL的操作看作0、加入 x ∉ S L x\not\in S_{L} xSL的操作看成1、删除操作看作-1,那么显然 t t t为桥即等价于其之前(包括自身)元素和为0

由此,不难找到桥,进而再维护 x ∈ S L x\in S_{L} xSL的最小值和 x ∉ S L x\not\in S_{L} xSL的最大值即可

可以使用平衡树维护操作序列,单次操作时间复杂度为 o ( log ⁡ n ) o(\log n) o(logn)

完全可追溯化:

考虑用树套树维护操作序列,先用一棵平衡树维护操作序列,将每一个节点子树对应的区间再使用一棵平衡树维护部分可追溯化的答案集合和删除的元素所构成的集合

(特别的,若删除时集合为空,则在后者中加入一个 ∞ \infty

该信息的维护参考前面的做法,可以做到 o ( log ⁡ 2 n ) o(\log^{2}n) o(log2n)的复杂度

为了方便,用二元组 ( S , S ′ ) (S,S') (S,S)描述两者,询问即要支持合并至多 log ⁡ n \log n logn个二元组 ( S , S ′ ) (S,S') (S,S)

考虑不断合并其中两个,不妨记为 ( S 0 , S 0 ′ ) (S_{0},S'_{0}) (S0,S0) ( S 1 , S 1 ′ ) (S_{1},S'_{1}) (S1,S1),则合并后的 S 0 ′ S'_{0} S0 S 1 S_{1} S1不会变化, 而 S 0 S_{0} S0 S 1 ′ S'_{1} S1即将两者放在一起排序后,最大的 ∣ S 0 ∣ |S_{0}| S0个属于 S 0 S_{0} S0、最小的 ∣ S 1 ′ ∣ |S'_{1}| S1个属于 S 1 ′ S'_{1} S1

更具体的,定义 max ⁡ x ( S ) \max^{x}(S) maxx(S) S S S中最大的 x x x个元素所构成的集合( min ⁡ x ( S ) \min^{x}(S) minx(S)同理),记合并后结果为 ( S , S ′ ) (S,S') (S,S),则有 S = S 1 ∪ max ⁡ ∣ S 0 ∣ ( S 0 ∪ S 1 ′ ) S=S_{1}\cup \max^{|S_{0}|}(S_{0}\cup S'_{1}) S=S1maxS0(S0S1) S ′ = S 0 ′ ∪ min ⁡ ∣ S 1 ′ ∣ ( S 0 ∪ S 1 ′ ) S'=S'_{0}\cup\min^{|S'_{1}|}(S_{0}\cup S'_{1}) S=S0minS1(S0S1)

(为了保证没有重复元素,可以将 ∞ \infty 的值调整为不同的值)

而合并两个集合显然是无法实现的,考虑将 ( S , S ′ ) (S,S') (S,S)都用若干个集合的并来描述

具体的,假设 ( S 0 , S 0 ′ ) (S_{0},S'_{0}) (S0,S0) ( S 1 , S 1 ′ ) (S_{1},S'_{1}) (S1,S1)中的每个集合分别用 k k k个集合的并描述,通过二分找到 max ⁡ ∣ S 0 ∣ ( S 0 ∪ S 1 ′ ) \max^{|S_{0}|}(S_{0}\cup S'_{1}) maxS0(S0S1)的最小值,并以此将 S 0 ′ S'_{0} S0 S 1 ′ S'_{1} S1对应的 k k k个集合划分为 2 k 2k 2k个集合,即可得到 ( S , S ′ ) (S,S') (S,S)分别用 3 k 3k 3k个集合来描述的结果

这样单次合并的复杂度即为 o ( k log ⁡ n ) o(k\log n) o(klogn),将其以分治的方式合并,即有 log ⁡ log ⁡ n \log\log n loglogn层,且其中从底向上第 i i i层的所有集合 k k k之和为 ( 3 2 ) i log ⁡ n (\frac{3}{2})^{i}\log n (23)ilogn,将其等比数列求和即可

综上,单次操作时间复杂度为 o ( log ⁡ 2 n ) o(\log^{2}n) o(log2n),单次查询时间复杂度为 o ( log ⁡ 3 n ) o(\log^{3}n) o(log3n)

参考文献

PYWBKTDA的笔记

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值