减半警报器 / zak 警报器

call me call me 和鬼街。老早之前就说要学,结果还是 dx 讲的。


它们解决了一类这样的问题:维护一堆集合,每次维护对一个集合中的所有数加上一个非负数(注意我们认为不同集合中的相同数是互通的,或者你可以理解为下标),同时支持询问一个集合所有数的和大于等于某个数的最小时间。

此时我们称呼对集合 \(x\) 在大于等于 \(y\) 时报警,那么这其实意味着一个报警器 \((x,y)\)

假设我们可以枚举修改的集合(通常我们都可以这么做,比如鬼街和 GYM 102452I,这些题目保证修改集合的大小和是 \(\omega(n)\) 的),但对所有包含它的集合进行操作通常是困难的。

考虑下放集合上的报警器就能解决这个问题了。注意到抽屉原理:集合 \(x\) 所有数的和大于等于 \(y\) 实际上意味着总有一个至少数达到了 \(\left\lceil\frac{y}{x}\right\rceil\)

于是我们给集合内的每个数设置一个 \(\left\lceil\frac{y}{x}\right\rceil\) 的小警报器,此时你发现大警报器报警的前提就是小警报器报警。

每次修改我们只需要检查它身上的小警报器是否报警,这可以用一个堆简单地维护,均摊每个警报器只会进一次堆出一次堆。当一个小警报器报警时,我们需要反向找出它的大警报器,然后把其他小警报器重新分配下去。可以使用可删堆来维护这个过程。

为了实现重新分配,我们需要重新计算 \(y\) 还剩下多少。这意味着我们需要维护该数上同源的报警器从上次报警到这次报警的增量。这可以简单地通过维护总增量,在插入报警器时扣掉不存在的贡献的办法实现。

lxl 证明了,对于一个大小为 \(s\) 集合 \(x\) 上的报警器 \((x,y)\),至多它会被重分配 \(\log_{\frac{s}{s-1}}y\) 次,结合可删堆,我们的复杂度通常是两只 log 的。

cxy 给出了一个简洁的复杂度证明:考虑每次小报警器报警说明至少减小了 \(\frac{y}{s}\)。这意味着我们每次报警器报警至少说明 \(y\gets \frac{s-1}{s} y\),于是复杂度是显然的。

P7603 [THUPC 2021] 鬼街

在折半警报器普及之后降紫了。

考虑小于等于 \(10^5\) 的数最多只有六个质因子,所以我们本质上就是在做大小为 \(6\) 的集合。考虑新增的警报器是 \(\mathcal O(q)\) 的所以不影响我们的复杂度,那么直接做折半警报器就可以了。

这个题被 zak 用 zak 警报器(二进制警报器)暴打,一会再来看吧。

GYM102452I. Incoming Asteroids

板子。

QOJ8035. Call Me Call Me

正解需要使用猫树分治,因为猫树可以把一个区间拆成 \(\mathcal O(1)\) 个区间。然后应用正常的折半警报器就可以了。

但是显然警报器问题可以离线后操作分块简单解决,这个题可以发现看似在线其实是假的,操作分块这样满 \(B\) 做一次的离线方式仍然被支持。所以我们可以用 https://www.cnblogs.com/imakf/p/17628483.html 轻易解决,并获得最优解。

posted @ 2025-02-08 11:28  Shunpower  阅读(72)  评论(0编辑  收藏  举报