二进制警报器详解

二进制警报器可以比减半警报器做到更优时间复杂度。

做法

[THUPC 2021] 鬼街 为例。

设警报器 \(u\) 监控的集合为 \(S_u\)\(|S_u|=k\),报警阈值 \(lim_u\),同时我们维护一个阈值 \(h_u\)。对于 \(i\in S_u\)\(val_i\gets val_i+w\) 认为它经过了 \((val_i,val_i+w]\)。当 \(i\) 经过 \(2^{h_u}\) 的倍数时检查 \(u\) 是否触发报警。

对于 \(i\in S_u\),求出 \(nxt(val_i,h_u)\) 表示 \(>val_i\) 的第一个 \(2^{h_u}\) 倍数的位置。那么我们要保持:

\(\sum\limits_{i\in S_u}(nxt(val_i,h_u)-val_i-1)< lim_u\)

这样在总和达到 \(lim_u\) 前必然会检查 \(u\)。为了方便我们维护一个 \(rest_u\) 表示距离到达 \(lim_u\) 还剩多少。

假设现在在 \(i\) 加上 \(v(v>0)\),经过了 \(2^{h_u}\),更新 \(rest_u\gets rest_u-(val_i-nxt(val_i-v,h_u)+1)\)

然后我们对这些 \(u\) 进行检查,如果 \(rest_u\ge nxt(val_i,h_u)-val_i\),则可以直接减少 \(rest_u\)。否则,当前的 \(h_u\) 已经不合法。暴力重构,重新计算阈值 \(lim_u\)。如果没有触发报警,减少 \(h_u\) 以满足条件,直到 \((2^{h_u}-1)\times k<lim_u\),那么条件必然成立。

一开始令 \(h_u=\log V\),然后降下来。对于每个 \(h_u=h_0\),最多检查 \(O(k)\) 次。而重构次数也是 \(O(\log V)\),每次重构 \(O(k)\)。总时间复杂度 \(O(mk\log V)\)

一些实现技巧:

\(nxt\)

ll nxt(ll v,int k){
    return v+(1ll<<k)-(v&((1ll<<k)-1));
}

\(i\) 经过的 \(h_u\) 集合:

ll high=63-__builtin_clzll(now[i]^(now[i]+w));//经过最高 2^k 的 k
ll Set=((2ll<<high)-1)&st[i];//st[i] 表示监控 i 的 h[u] 集合

完整代码:https://loj.ac/s/2415933

本质

减半警报器和二进制警报器解决了一类集合的映射问题。其中集合 \(A\) 只对应 \(B\) 中的少量元素(\(k\) 个),而 \(B\) 却可能对应 \(A\)大量元素(如质因子 \(2\) 对应了所有偶数)。每次在 \(B\) 上修改,同时一个个取出 \(A\) 中的元素。瓶颈在于 \(O(|A|)\) 枚举元素判断是否应当删除。

于是我们还是希望用 \(B\) 去更新 \(A\),通过均摊 \(A\) 中元素被更新的次数保证复杂度。减半警报器采取的策略是找到被删除的必要条件 \(\exist i\in S_u,\Delta val_i\ge \lceil\frac{lim_u}k\rceil\),只有满足该条件才检查 \(u\);二进制警报器的策略则是找到必要条件 \(i\) 经过 \(2^{h_u}\) 的倍数,只有满足该条件才检查 \(u\)

这样,\(u\) 被检查的次数不会很多,时间复杂度也就得以保证了。

posted on 2025-09-02 15:09  xinjingzhu  阅读(82)  评论(0)    收藏  举报