二进制警报器 学习笔记

参考文章:二进制警报器 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;
}

posted @ 2026-10-12 21:39  Ff472130  阅读(3)  评论(0)    收藏  举报