前天 uq 里有人问这个。
单点修改区间 \(min\),能不能做到修改 \(O(1)\) 查询 \(O(\sqrt n)\)。
后来和 @critno 私下讨论了一会,得到了 \(O(1)\) 修改 \(O(n^{\epsilon})\) 查询的算法,并进行了简化版的实现。
算法本质是操作序列分块。
先考虑一个朴素算法。
原序列不动,跑 RMQ。
每当有一个询问加入时,视作散点单独计算。
当我们攒够 \(O(\sqrt n)\) 个散点的时候就把这些散点视作一个块,排个序然后跑 RMQ。
攒够 \(O(\sqrt n)\) 个块之后就整体重构。
这里有一个小问题,加入一个散点的同时也意味着要删除某个位置的数。
所以我们的 RMQ 需要支持 \(O(n)\) 预处理和 \(O(1)\) 删除一个数(把某个位置的值变成 \(+\infin\))
这个可以使用多层分块实现。
块内排序,维护一个指针指向最小值。
删数采用延迟删除,打个 \(tag\)。
查询的时候再移动指针即可。
删除均摊 \(O(1)\),查询 \(O(n^{\epsilon})\)。
同时我们发现一个数至多出现在一个块里(因为每次先删除再加入)
所以修改复杂度均摊 \(O(1)\)。
至于查询,需要遍历每一个块,二分,最后再扫一遍散点。
二分可以使用分散层叠优化,最后得到复杂度 \(O(n^{\frac{1}{2} + \epsilon})\)。
排序需要使用基数排序。
在多层分块层数 \(O(1)\) 的情况下基数排序的复杂度也是线性。
考虑优化。
我们全新推出了中块,大块和超大块。
长度分别为 \(O(n^{\frac{1}{4}})\),\(O(n^{\frac{2}{4}})\),\(O(n^{\frac{3}{4}})\)。
攒够 \(O(n^{\frac{1}{4}})\) 个散点即可兑换一个中块,攒够 \(O(n^{\frac{1}{4}})\) 个中块即可兑换一个大块,攒够 \(O(n^{\frac{1}{4}})\) 个大块即可兑换一个超大块。
攒够了 \(O(n^{\frac{1}{4}})\) 个超大块我们再重构。
不难发现其实就是把操作序列上的分块改成多层分块。
上述算法的其他部分不用改。
查询复杂度降低至 \(O(n^{\epsilon})\)。
由于多层分块的实现比较复杂,我们只测试了 \(层数=2\),即普通分块的情况。
提交记录
这个题修改次数过少,为了充分测试重构的正确性将块长调整至 \(O(n^{\frac{1}{4}})\)。
参考文献:
- uq