警报器
警报器问题一般指:
- 设置警报:当 \(a_{i_1} + \ldots + a_{i_k} \ge S\) 时,该警报报警,并引发一些事件和操作。
- 操作:给 \(a_i\) 加上 \(x\) 或其他对 \(a_i\) 的修改,修改要使 \(a_i\) 不降。这些操作可能使警报器报警。
常用的警报器有两种:折半警报器与二进制警报器,后者是 zak 发明。两者在不同应用场景有不同的优劣势,前者虽然复杂度较劣,但代码容易、思维简单;后者复杂度优,但代码通常难于前者,且相对前者,不太容易被数据结构优化。
两种警报器的共有思想是:给集合中每个元素设置增量阈值,使得报警前至少一个元素的阈值被达到,并在单个元素增量达到其阈值时判定警报器是否应该报警。
折半警报器
也不知道为什么叫折半......大概是从 \(k = 2\) 的情况扩展而来。
基本思想是:要使得 \(i_1, \ldots, i_k\) 的总增量 \(\ge \Delta\),则至少一个下标的增量 \(\ge \lceil{\Delta \over k}\rceil\),于是给每个元素设置该阈值。每次达到阈值,把集合中元素的新阈值按照剩余增量 \(\Delta'\) 重构,由于 \(\Delta' \le \Delta \cdot \frac{k - 1}{k}\),故经过 \(O(\log_{\frac{k}{k - 1}} V) = O(k \log V)\) 轮后,\(\Delta\) 一定变为 \(0\)。
如何维护过程?由于阈值是单调递减的,所以不能打懒标记,只能每次重构时把新阈值放进每个元素。而操作时该元素应该找到能触发的最小阈值,故应该开 \(n\) 个堆来处理。于是总复杂度是 \(O(1) - O(k^2 \log q \log V)\)。
二进制警报器
折半警报器比较慢的原因是:重构次数太多,甚至与 \(k\) 有关。想要去掉一个 \(k\),就应该让阈值与 \(k\) 无关。
考虑根据二进制设阈值,假设需要的和是 \(S\),记 \(f(x, i)\) 表示第一个 \(\ge x\) 的 \(2^i\) 倍数。当警报器重构时,找到最大的非负整数 \(h\),使得 \(\sum_{j} f(a_{i_j}, h) < S\),把这个 \(h\) 称为每个元素的阈值。操作时,考虑 \(a_x \to a_x + y\) 跨过了哪些 \(2^b\) 的倍数,对于这样的 \(b\),尝试更新所有 \(h = b\) 的警报器,如果发现把 \(f(a_x, h)\) 变为 \(f(a_x + y, h)\) 还是能让和 \(< S\),就不管这次更新;否则,就需要更新该警报器的 \(h\),这里 \(h\) 会至少减少 \(1\)。当 \(h\) 变为 \(-1\) 时,警报器就报警了。
计算一下复杂度,容易发现重构次数只有 \(O(\log V)\) 次了,且不需要用堆,只需用长 \(O(\log V)\) 的 vector 存阈值。总复杂度变为 \(O(\log V) - O(k \log V)\),就很优了。
一个简单的板子,因为全都使用了 vector,所以跑得比较慢:
代码
struct ds {
int n, m;
std::vector<int> h, ans;
std::vector<i64> a, rem, limit;
std::vector<std::vector<int>> S;
std::vector<std::array<std::vector<int>, 51>> w;
ds(int n_) {
n = n_;
m = 0;
a.assign(n, 0);
w.assign(n, {});
}
void calc(int k) {
rem[k] = 0;
for (auto i : S[k]) {
rem[k] += next(h[k], a[i]) - 1;
}
}
void rebuild(int k) {
while (rem[k] >= limit[k]) {
h[k]--;
if (h[k] < 0) {
ans.push_back(k);
return ;
}
calc(k);
}
for (int i : S[k]) {
w[i][h[k]].push_back(k);
}
}
void add(std::vector<int> idx, i64 dt) {
i64 prev = 0;
for (auto &i : idx) {
prev += a[i];
}
rem.push_back(0);
S.push_back(idx);
limit.push_back(prev + dt);
h.push_back(50);
calc(m);
rebuild(m);
m++;
}
void modify(int x, i64 y) {
int b = std::__lg(a[x] ^ (a[x] + y));
std::vector<std::pair<int, int>> f;
for (int i = 0; i <= b; ++i) {
for (int k : w[x][i]) if (h[k] == i) {
f.push_back({i, k});
}
w[x][i].clear();
}
a[x] += y;
for (auto [i, k] : f) {
rem[k] += next(i, a[x]) - next(i, a[x] - y);
if (rem[k] >= limit[k]) {
rebuild(k);
} else {
w[x][i].push_back(k);
}
}
}
};
例题
难度随机排序。

浙公网安备 33010602011771号