操作树学习笔记
Last Update:
Word Count:
Read Time:
引入
有些题目有这样的特点:
- 允许离线
- 需要回退历史版本
这种情况下,我们可以离线所有操作,建立操作树,每个版本对应一个节点,其父亲就是其来源的版本。对于修改操作,回溯时回退即可。
当然,如果强制在线的话还是上可持久化数据结构吧。
例题
[HBCPC2024] Enchanted
第一次见到这个 trick 是在这个题。
不考虑操作树的话,剩下的和 Mark and Professor Koro 还是很像的。
对于这种两个相同的数合并成 +1 的数的操作,我们可以把它看成二进制意义下的加法,然后进行维护。
这道题值域很小,所以可以直接把二进制用整数表示。
考虑建立操作树,操作四就解决了。
操作三是单点修改,比较简单。
操作一就是表示成二进制以后求和找最高位。
操作二就是表示成二进制以后求和,然后找第 位向上的连续位数。
单点修区间和,树状数组即可。
复杂度
1 |
|
「TOCO Round 1」History
考虑建立操作树,操作三就解决了。
操作一是单点修改,比较简单。
考虑操作二如何实现。
看到相同深度想到 bfs 序,那么这些点事实上就是 的 级祖先的子树内和 在相同深度的点。
不难发现这些点的 bfs 序是连续的。
然后考虑怎么得到 级祖先的子树内和 在相同深度的点。
可以发现,这些点的 dfs 序也是连续的。
所以我们把所有和 在相同深度的点拿出来,然后二分找到 dfs 序在 的点,那么操作二就变成了区间查询。
别忘了把 级祖先的子树内和 在相同深度的点扣掉,因为这些点并不合法。
复杂度
有些细节,例如 可以不合法,而且还能为 0,都需要特判。
upd:这里写的麻烦了,没必要开 vector 把所有深度的所有点存下来,可能当时写的时候还没太懂。
我们求出每个深度对应的 bfs 序区间,然后把 bfs 序和 dfs 序对应起来。
这样在查某个深度的时候,只需要在 bfs序的区间上二分即可。
具体实现可以看[Cnoi2019] 雪松果树。
1 |
|
Persistent Bookcase *2200
建操作树后暴力 bitset 维护即可。
对一行取反可以先把一个全为 1 的 bitset 处理出来,然后异或这个 bitset 即可。
1 |
|
可持久化并查集
没有强制在线,所以考虑操作树。
维护个可撤销并查集就行了。
没写代码。
TTM - To the moon
没有强制在线,所以考虑操作树。
然后线段树维护就行了。
然而当初做这道题是为了学主席树如何区间修改(标记永久化)。
[Ynoi2014] 等这场战争结束之后
如果没有回撤,那么这是[HNOI2012] 永无乡,并查集+线段树合并即可。
那我暴力线段树合并+分裂行不行?不行,因为每次撤销都可以卡成 次分裂,那就寄了。
那我 LCT 上维护 set 行不行?不行,理由同上。
我们真的需要 LCT 维护这个东西吗?事实上不用,因为我们只关心连通性,所以可撤销并查集就足够了。
那 kth 怎么解决?线段树用不了了,那就值域分块!
我们对每个联通块维护所有整块的信息,合并时暴力合并。
查询也很简单,因为相当于查全局 kth,直接枚举每个整块,然后找到落在哪个块时就枚举这个块里的值。
枚举值的时候要判断这个值对应的点是否在联通块内,所以要给值和点对应一个关系(下面提到了)。
时间复杂度 ,空间复杂度 。
块长取 时最优。
时间复杂度 ,空间复杂度
代码倒是没有很难写,主要是卡空间卡的要死。
卡空间的一些技巧:
- 块长调大点
- 能开 short 的数组开 short
- 别用 vector 存图,用链式前向星
- 能不存的就别存,例如每个块的起始终点之类的
还有一些需要注意的点:
- 离散化时,相同的数不能视为一个,因为在查询时,我们查到一个整块的里面时,需要知道每个值对应的下标是否在当前连通块里,所以编号要注意。
- 别写 UB 或你弄不懂到底会怎么运行的代码。例如我参考的题解里写了一句
a[i]+=cnt[a[i]]++;
,这东西你真能弄清吗。
因为一直卡不过去所以最后参考了题解,代码也跟题解差不多了,而且写的也很丑,所以不放代码了。
参考文章: