好玩的数据结构
(注:为了方便就先把图咕了)
猫树
(在 Illusory_dimes 学习 SAM 时找例题找到的 cmd 的神仙题顺便学的)
基本信息
这是一个比较良心的数据结构,代码属于比较易懂也好打。只是有可能目前的应用范围有点小。
猫树呢,大概是一个能 \(O(n\log n)\) 建树, \(O(1)\) 查询,不太能修改(你非要的话就 \(O(n)\) ),维护信息需要满足结合律且能在可接受复杂度合并。
算法实现
建树
以查询出发,我们假设我们答案要的区间是 \([{\rm l},\ {\rm r}]\) ,那么假设我们已经能知道 \([{\rm l},\ {\rm mid}]\) 和 \([{\rm mid} + 1,\ {\rm r}]\) 的答案,那是不是直接合并就行了。
我们想象线段树建树是 \([{\rm L},\ {\rm R}]\) 递归到 \([{\rm L},\ {\rm Mid}]\) 和 \([{\rm Mid} + 1,\ {\rm R}]\) ,以左边这个小区间为例,如果我们直接处理 \([{\rm L}-{\rm Mid},\ {\rm Mid}]\) 的答案,因为最多递归 \(\log n\) 次,整个时间复杂度就是 \(O(n \log n)\) ,还是比较能够接受的。
然后我们发现假如一个答案区间 \([{\rm l}, {\rm r}]\) 的 \({\rm l}\) 和 \({\rm r}\) 在递归的同一小区间,就并不是我们处理时能取到的答案,直到最后递归到了一个区间 \({\rm l}\) 和 \({\rm r}\) 被分开了,我们就能找到一个 \({\rm mid}\) 能合并两边的答案,从而我们就达到了目的。
然后我们思考怎么存答案,对于每一层,不同的区间所在范围互不交叉,所以我们以层数为第一维,具体的哪一个位置为第二维。
查询
但是没完,我们要求的是 \(O(1)\) 查询,但是我们好像不知道怎么一下子定位在哪个区间能合并。
能发现两个点作为两个叶子节点,他们的 \({\rm lca}\) 就是答案所在的区间,因为此时两个点所在的区间被合并成一个大区间了,正好就是有一个 \({\rm mid}\) 大于 \({\rm l}\) 且小于 \({\rm r}\) 。
那是不是什么 ST 表就可以了??其实还可以更简单!!
好像一些性质没用上,我们来想假如两个值在一个区间被分开了,那他们会有什么联系。
好像没有什么性质吧,因为边界是 \([1,\ n]\) ,那就改成 \([1,\ 2 ^ m]\) ,其中是 \(2 ^ m\) 大于 \(n\) 中最小的数。本质上时间复杂度不会变,但是加入两个数被分开,假如目前是第 \(k\) 层,那就是第 \(k\) 高位不一样。
这就相当于两个异或之后前 \(k - 1\) 位全部消失,那我们只要预处理二进制位数,我们就能很快得到 \(k\) 的值,因为答案存的方式正好需要 \(k\) ,正好就可以马上算出答案了,也就是真正意义上的 \(O(1)\) 了。
修改
修改你大爷,以单点修改为例,影响的地方从第一层一直到自己所在的叶子,所在区间大小和为:
搞屁,修改都是 \(O(n)\) 的,那就不修改(当然什么修改次数很小倒是可以考虑考虑)
猫树分治
前景提要:某些卡空间但是没有要求强制在线的题
其实处理方法很简单,就有点归并排序的意思,每次递归两个小区间,就扫一遍询问,把答案能算的算了,不能算了分成两拨继续递归就好啦。
这样的话时间复杂度不变,空间复杂度从原先 \(O(n \log n)\) 变成 \(O(n)\) 了。
例题
GSS1(最大子段和)
GSS5(区间端点带限制最大子段和)
好吃的题目(猫树分治)
[CmdOI2019]口头禅(猫树分治 + SAM )
全局二叉平衡树
(听了 After_glow 的链分治顺便学的)
前置背景
有些时候,对于链分治问题,我们常常会为了究竟是用重链剖分还是 LCT 而纠结,因为一个理论复杂度大但是常熟小,另一个理论复杂度小但是常熟巨大。
实质上,树剖时用数据结构维护局部(重链),而 LCT 是对整棵树维护形态变化的 splay ,所以说前者不能一次性解决问题,后者在形态上的调整又太费时间。
但是对于链修改链查询的问题,我们是否能用一个形态不变的数据结构维护全局的答案,于是就有了这玩意。。
实现
似乎我们想要在 LCT 上做手脚是不太可能了,来看树剖,如果一个点轻儿子总 \(siz\) 过大,全部把它放到重链线段树末尾肯定不优,我们可以考虑一下更换一下重链的维护方式,能让轻儿子的影响也降到最小。
我们令每个点的权值就是轻儿子 \(siz\) 总和,然后每次找到重链上的带权重心作为该层的节点,然后递归下去,这让的话每次递归一下总 \(siz\) 会减半,树高同样也是 \(\log siz\) 级别的。
重链之间的连边就还按照虚边的连法照做就行了,这样的话总树高仍然是 \(\log n\) 级别的,所以单次询问的时间能降下来,并且常数也很小。
例题
看着吧,找到了就加。(多找找 LCT 的题看)
莫队二次离线
( caoyue 讲题第二次遇到,还是选择来细学一下)
背景
平时又很多离线问题能够用莫队干爆,但是有些要求维护的东西并不能做到 \(O(1)\) 修改,从而让时间复杂度变得有点棘手。
求区间逆序对个数。
莫队确实能做,修改一个点,整一个差分就能 \(\log\) 找新答案,但是却成了不可避免的瓶颈,因为这样复杂度变成 \(\sqrt{n} \cdot \log n\) 的,会变得不好看。
那考虑 \(\sqrt{n}\) 怎么去解决这个问题,所以就有了这玩意。
实现
还是以求区间逆序对个数为例,朴素莫队的做法大概就是对于区间 \([l,\ r]\) ,我们加进来 \(r + 1\) 这个点的时候,答案就会多 \([l,\ r]\) 和 \(r + 1\) 中逆序对的个数。
考虑差分,可以转化成 \([1,\ r]\) 和 \(r + 1\) 中逆序对的个数减去 \([1,\ l - 1]\) 和 \(r + 1\) 中逆序对的个数.
前者是固定的值,但是后者可能会有多次询问,如果每次都这样 \(\log\) 询问一次,确实有点浪费,我们或许可以考虑考虑把这些操作再次离线下来。
现在来说操作自由度变高了,可以来考虑考虑怎么高效利用这个自由度。
首先明确目的,我们是一定要能 \(O(1)\) 查询,至于插入,因为我们只要插入 \(n\) 个数就行了,甚至我们可以接受 \(\sqrt{n}\) 级别的插入。
啊,值域分块,怎么做就不用细讲了。
可能有些出题人(我不说是谁)卡了空间,所以我们不可能存下来所有 \(n \cdot \sqrt{n}\) 个操作。但是我们发现也不需要,因为每次莫队移动是对于单个端点的连续移动,所以可以每次把移动区间压到一起,这样空间就是现行的了。

浙公网安备 33010602011771号