二进制警报器 学习笔记
参考文章:二进制警报器 by zhoukangyang。
二进制警报器能够在线解决以下问题:
给定一个长度为 \(n\) 的数组,\(q\) 次操作,每次操作如下:
- 在数组的一个给定位置加上一个非负整数 \(v\);
- 给定 \(k\) 个位置,要求当数组的这 \(k\) 个位置上和不小于此次操作给定阈值 \(v\) 时输出该操作编号。
二进制警报器解决此问题可以做到均摊 \(O(1)-O(k\log V)\),下面具体介绍这个算法。
将“判定总和是否达标”换成“判定是否有人越线”
称第二种操作为监控操作,给出的位置为被监控的位置,由于被监控位置的总和不好维护,所以考虑给每个被监控位置画一些线,对于每次加操作,只关心“这次加法有没有越过某条线”。
- 越线是单位置事件,一次加法跨了哪些线可以立刻知道,代价为 \(O(1)\);
- 如果能够保证“总和达标之前,必然有某个位置先越线”,那么不越线的加操作就不需要花费额外时间进行维护。
于是我们需要关心:线怎么画,越线后需要做什么。
按 \(2\) 的幂倍数划分线
设一个监控操作监控了 \(k\) 个位置 \(a_1,a_2,\dots,a_k\),这些位置的和距离监控阈值 \(v\) 还差 \(r\)(真实剩余)。
定义一个位置的乐观值为不越线的前提下最多能涨到的位置,记乐观剩余 \(rem\) 为监控阈值 \(v\) 减去所有乐观值之和,由于不越线时必然有真实值之和不超过乐观值之和,所以真实剩余一定不小于乐观剩余,即当 \(rem>0\) 时,这个监控操作一定不可能达到监控阈值。
考虑将线画在 \(2^h\) 的倍数,称 \(h\) 为这个监控的层数,\(h\) 越大线越稀,反之线越密。
不越线时,每个位置最多再增加 \(2^h-1\),所以总和最多增加 \(k\times (2^h-1)\),若这个总和超过了真实剩余 \(r\),说明可能在不越线的前提下总和超过监控阈值,那么需要降低 \(h\),增加线的密度,从而使监控更准确,由于越线会带来额外的复杂度,所以我们希望线在保证正确性的前提下尽可能稀疏,所以一个监控操作的层数即为最大的满足条件的 \(h\),随着加操作的进行,真实剩余 \(r\) 逐步减少,从而可能导致 \(h\) 逐步降低,对应了越接近目标监控越精确的机制。
越线后的处理
当一个位置值从 \(A\) 变为 \(B\),碰到了一条线 \(L(A<L\le B)\),那么我们将这个监控操作拿出来,做以下的事情:
- 把“从线 \(L\) 到 \(B\)”的增量(\(B-L+1\))全部从 \(rem\) 里扣掉;
- 把这个位置的乐观值重新预支到它的下一条线,即再扣掉 \(B\) 到下一条线减 \(1\) 的富余量,继续等待下次越线;
- 若 \(rem\) 不超过 \(B\) 到下一条线减 \(1\) 的富余,就重新计算真实值,若真实值已经达到监控阈值则输出编号,否则根据真实剩余重新计算对应 \(h\) 及乐观剩余,此时 \(h\) 会降低。
正确性是显然的,根据真实剩余不小于乐观剩余,在乐观剩余大于 \(0\) 时直接减少,不超过 \(0\) 时重构,一定不会漏报或误报。
复杂度分析
- 每次单点加,不考虑越线的情况时 \(O(1)\) 的;
- 对于每个监控操作的一个层 \(h\),由于一个位置越线两次后真实值增加的值一定不小于 \(2^h\),所以经历 \(O(k)\) 次越线就会使真实剩余 \(r\) 减半,需要重构,所以越线而不造成重构的情况只会出现 \(O(k)\) 次,每次复杂度 \(O(1)\),每层总复杂度为 \(O(k)\);
- 每次重构会导致层数降低,单次重构复杂度 \(O(k)\),所以每个监控操作每层总复杂度 \(O(k)\)。
由于层数为 \(O(\log V)\) 级别,所以每个监控操作总复杂度 \(O(k\log V)\)。
实现细节
- 用 vector 或链表在每个位置的每个 \(h\) 上维护对应的所有监控;
- 一个加操作使得一个位置从 \(A\) 变成了 \(B\),那么触碰线对应 \(h\) 的范围恰好为 \(0\) 到 \(A,B\) 异或值的最高位;
- 重构时采用懒删除方式或链表来保证复杂度;
- 维护每个位置在哪些 \(h\) 上有对应监控,从而利用位运算 \(O(1)\) 找到这些监控。
例题
[THUPC 2021] 鬼街
「FAOI-R15」self-dissociation
参考代码([THUPC 2021] 鬼街)
Code
#include<cstdio>
#include<algorithm>
#include<vector>
#include<bitset>
using namespace std;
#define ll long long
#define ull unsigned long long
#define uint unsigned int
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=1e5+10;
constexpr int inf=1e9+10;
inline void read(uint &x) {
x=0;
char ch=getchar();
while (ch<48) ch=getchar();
while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}
vector<int> d[N];
inline void init(int n) {
bitset<N> vis;
f(i,2,n) {
if (vis[i]) continue;
for (int j=i;j<=n;j+=i) {
vis.set(j);
d[j].push_back(i);
}
}
}
uint n,m,idx;
ull a[N],ok[N];
ull px[N],pv[N];
ll ph[N],pr[N];
vector<int> vec[N][34];
inline int high_bit(ull x) {return 63-__builtin_clzll(x);}
inline ull next_bit(ull x,int h) {return x+(1ull<<h)-(x&((1ull<<h)-1));}
vector<int> clr;
inline void rebuild(int id) {
ll tmp=pv[id],x=px[id];
for (int p:d[x]) tmp-=a[p];
if (tmp<=0) return ph[id]=-1,clr.push_back(id),void();
int sz=d[x].size(),h=ph[id]-1;
while (tmp<=1ll*sz*((1u<<h)-1)) h--;
for (int p:d[x]) {
tmp-=(next_bit(a[p],h)-1-a[p]);
vec[p][h].push_back(id);
ok[p]|=(1ull<<h);
}
pr[id]=tmp;ph[id]=h;
}
inline void update(int pos,ull k) {
if (!k) return;
ull A=a[pos],B=a[pos]+k;a[pos]=B;
uint hb=high_bit(A^B);
uint mask=((1ull<<(hb+1))-1);
uint flag=mask&ok[pos];
vector<int> tmp;
while (flag) {
uint lb=(flag&-flag);flag^=lb;ok[pos]^=lb;
int d=__builtin_ctz(lb);
for (int id:vec[pos][d]) if (ph[id]==d) {
pr[id]-=B-(next_bit(A,d)-1);
tmp.push_back(id);
}
vec[pos][d].clear();
}
for (int id:tmp) {
ll val=next_bit(B,ph[id])-B;
if (pr[id]>=val) {
pr[id]-=(val-1);
vec[pos][ph[id]].push_back(id);
ok[pos]|=(1u<<(ph[id]));
}
else rebuild(id);
}
}
inline void add_p(uint x,uint y) {
idx++;px[idx]=x;pv[idx]=y;ph[idx]=31;
for (int p:d[x]) pv[idx]+=a[p];
rebuild(idx);
}
int main() {
read(n);read(m);init(n);
uint last_ans=0;
while (m--) {
uint op,x,y;
read(op);read(x);read(y);y^=last_ans;
if (op==1) add_p(x,y);
else {
for (int p:d[x]) update(p,y);
sort(clr.begin(),clr.end());
printf("%d ",(int)(clr.size()));
for (int x:clr) printf("%d ",x);
putchar(10);last_ans=clr.size();
clr.clear();
}
}
return 0;
}

浙公网安备 33010602011771号