已完成今日二进制警报器大学习
Redshift_Shine · · 算法·理论
前言
二进制警报器是 zky 在 WC2025 前不久提出的一个掀起了巨大波澜的算法,该算法使得一道来自于四年前的经典折半警报器大时限题目鬼街的限时可以被压制到 1s 以下。
对于该算法,zky 本人已经在他的博客中给出了完善的证明,故本人在这篇文章中将给出该算法在鬼街一题中的一种参考实现,在该题的所有样例上的运行总时长略慢于 zky 的代码。此处为我的提交记录。
代码解析
变量声明
int n, m, idx;
using ll = long long;
ll sm[N], tar[N];
vector<int> res, fac[N], zr;
vector<int> id[N][M];
int rt[N], sid[N];
bool vis[N];
对于上述的变量,其意义如下:
::cute-table{tuack}
| 变量名称 | 意义 |
|---|---|
| 如题目所示 | |
| 目前警报器的最大编号 | |
| 目前位置 |
|
| 警报器 |
|
| 本次查询报警的警报器编号 | |
| 自然数 |
|
| 所有监控范围包含 |
|
| 警报器 |
|
| 警报器 |
|
| 警报器 |
函数 \operatorname{calc}
ll calc(int x, int shd)
{
ll tsm = 0;
for (auto &i : fac[x])
{
tsm += sm[i] + (1ll << shd) - (((1ll << shd) - 1) & sm[i]) - 1;
}
return tsm;
}
该函数的作用是计算自然数
特别的,若
函数 \operatorname{proc}
void proc(int x, ll v)
{
ll msk = (sm[x] ^ (sm[x] + v));
sm[x] += v;
vector<int> tmp;
for (int i = 0; (1ll << i) <= v or ((msk >> i) & 1); i++)
{
tmp.swap(id[x][i]);
for (auto &j : tmp)
{
if (vis[j] or sid[j] != i)
continue;
if (calc(rt[j], 0) >= tar[j])
{
res.emplace_back(j);
vis[j] = true;
continue;
}
for (; ~sid[j] and calc(rt[j], sid[j]) >= tar[j]; sid[j]--)
;
if (sid[j] == i)
{
id[x][i].emplace_back(j);
continue;
}
for (auto &k : fac[rt[j]])
id[k][sid[j]].emplace_back(j);
}
tmp.clear();
}
}
该函数的作用是在位置
判断区间
- 判断
y\ge 2^p 。 - 判断从
x 到x+y 时2^p 对应的二进制位是否发生反转。
上述两个条件满足其一即包含
容易写出
通过上式可得出结论:若
该函数内声明的变量
我们从低到高枚举每一个越过的
考虑到可能会存在重构后
枚举警报器时,若枚举到已经发出警报的警报器显然可以跳过;同时,若该警报器目前的
最后一步处理时,若
主函数
void run()
{
cin.tie(0);
cout.tie(0);
ios::sync_with_stdio(0);
cin >> n >> m;
for (int i = 2; i <= n; i++)
{
if (fac[i].size())
continue;
for (int j = i; j <= n; j += i)
fac[j].emplace_back(i);
}
for (ll i = 1, op, x, y, la = 0; i <= m; i++)
{
cin >> op >> x >> y;
y ^= la;
if (op)
{
idx++;
if (!y)
{
zr.emplace_back(idx);
continue;
}
rt[idx] = x;
for (auto &j : fac[x])
{
tar[idx] += sm[j];
}
tar[idx] += y;
sid[idx] = M - 1;
for (; ~sid[idx] and calc(rt[idx], sid[idx]) >= tar[idx]; sid[idx]--)
;
for (auto &j : fac[x])
{
id[j][sid[idx]].emplace_back(idx);
}
continue;
}
res.clear();
zr.swap(res);
for (auto &j : fac[x])
proc(j, y);
cout << res.size() << ' ';
la = res.size();
sort(res.begin(), res.end());
for (auto &j : res)
{
cout << j << ' ';
}
cout << '\n';
}
}
使用
至此,本题完成。
碎碎念
看到二进制警报器的时候还是初三的寒假。那时由于我对这道题不感兴趣,所以就没有做出来。
再后来,各个中考考试科目接踵而来。在中考的压力之下我不得不将编程放下一段时间。最终成绩出来,在数学/英语/物理/化学加起来扣了
这一切都结束之后,我每天都随机找题做,随到了鬼街,想起了二进制警报器。于是,在昨天,我下定决心做出这道题,最后花了
不管怎么说,我的 OI 的故事,现在才刚刚开始!