二进制警报器
我们希望在线解决如下形式的问题:
给定长度为 \(n\) 的数组和 \(q\) 次操作,每次操作如下:
- 给数组一个位置加 \(v\)。
- 给定不超过 \(k\) 个位置,要求在数组这些位置的总和超过 \(v\) 的时候输出该操作的编号。
众所周知,折半警报器能在均摊做到 \(\mathcal O(1) - \mathcal O(k^2\log q\log V)\)(\(x - y\) 表示操作一复杂度为 \(x\),操作二复杂度为 \(y\))。但我发现了一个 \(\mathcal O(1) - \mathcal O(k\log V)\) 的算法,我们就称其为 "二进制警报器" 吧。
对于一个二操作,设其对应位置为 \(pos_1,...,pos_d\)。对于该操作,维护一个阈值 \(h\),只要 \(a_{pos_i}\) 的值经过(\(a_x \to a_x+w\) 的时候,我们认为它经过了 \((a_x,a_x+w]\))\(2^h\) 的倍数时 "报警"。如果目前的 \(h\) 能让所有 \(a_{pos_i}\) 在都不报警的前提下总和超过 \(v\),那么将 \(h\) 降低 \(1\)。
对于每个 \(h=h_0\),报警次数总和是不超过 \(\mathcal O(k)\) 的:\(h\) 降到 \(h_0\) 时,\(v - \sum_{i=1}^{k}a_{pos_i}\) 应是 \(\mathcal O(k2^{h_0})\) 的;而一个元素报警两次时,它必然增加了至少 \(2^{h_0}\)。
对每个 \((\text{位置},h)\) 开个 vector
维护警报器并利用二进制优化修改时找报警器的复杂度,复杂度是 \(\mathcal O(1) - \mathcal O(k\log V)\)。
这个结构能对我目前看到的所有减半警报器题目做出优化:
- 2019-2020 ICPC Asia Hong Kong Regional Contest I:直接的应用,能做到 \(\mathcal O(n+m\log V)\)。
- THUPC 2021 鬼街:直接的应用,能做到 \(\mathcal O(n+q\omega(n)\log V)\)。
- 300iq Contest 2 F:把
vector
改成链表,结合启发式合并可以做到 \(\mathcal O((n+m)\log V)\)。 - 2022 CCPC Mianyang Onsite B:这题要难优化一些。考虑直接建线段树,然后将每个人的需求拆到线段树的节点上。这样能做到修改 \(\mathcal O(\log n)\),但是查询要 \(\mathcal O(\log^2 n)\)。考虑平衡复杂度,把线段树的二叉改成 \(\sqrt{\log n}\) 叉,并对线段树的每个儿子区间都维护其总和。这样我们就能将每个人的需求拆到 \(\mathcal O(\frac{\log n}{\log\log n})\) 个点上了,而一次修改只会修改 \(\mathcal O(\frac{\log^2 n}{\log\log n})\) 个节点。总复杂度 \(\mathcal O(\frac{n\log^2 n}{\log\log n})\)。
- EZEC Round 12 F:和上一题类似,建立多叉树平衡复杂度,\(\mathcal O(\frac{m\log n\log V}{\log\log V})\)。