题解:P11160 機械生命体
操作一和操作二是平凡的,大多数数据结构都支持插入和删除。
题目中的 \(\operatorname{lowbit}\) 即为二进制最低位 \(1\) 所在的位置,而 \(0\) 因为没有 \(1\) 所以可以理解为 \(\operatorname{lowbit}\) 在无穷远处。
操作四中要想让 \(\operatorname{lowbit}(x\oplus y)\) 尽可能大,即 \(y\) 的从低位到高位与 \(x\) 尽可能相同。由此不难想到用 Trie 从低位到高位维护每个集合中每个数的数量。查询时尽可能寻找低位与 \(x\) 相等的 \(y\),未找到时当前位即为 \(\max_{y\in S}\operatorname{lowbit}(x\oplus y)\)。
插入和删除可以类比权值线段树,动态开点并在对应位置加一或减一。以上三种操作时间复杂度单次 \(\mathcal{O}(\log x)\)。

接下来只剩操作三还没有考虑到。
先考虑 \(k=1,v=1\) 的特殊情况。
因为对于任意的 \(x,y\) 都有 \(\operatorname{lowbit}(x\oplus y)\ge 1\),所以此操作相当于全局 \(+1\)。
如何全局 \(+1\)?交换左右子树然后对左子树(即此前的右子树)重复此操作直到到达叶子节点。
为什么?对于每个二进制数,\(+1\) 相当于将从低位到高位第一个 \(0\) 置 \(1\) 并将此前所有 \(1\) 置 \(0\)。左子树(最低位为 \(0\))\(+1\) 后最低位为 \(1\),挪到右子树;右子树(最低位为 \(1\))\(+1\) 后最低位置 \(0\) 并进位(即递归重复操作)。

设根节点子树中点的集合为 \(Y\),则其儿子子树中的点的集合为 \(\{y\in Y\mid \lfloor\frac{y}{2}\rfloor\}\),左右儿子的区别在于最低位是 \(0\) 或 \(1\)。
推广到 \(v\ne 1\) 的情况。
运用我们惊人的注意力可以注意到 \(y+v\) 等价于 \(2(\lfloor\frac{y}{2}\rfloor+\lfloor\frac{v}{2}\rfloor)+(y\bmod 2)+(v\bmod 2)\),因此 \(+v\) 可以转化为在根节点执行全局 \(+(v\bmod 2)\)(\(+0\) 等于没加)后对左右子树执行全局 \(+\lfloor\frac{v}{2}\rfloor\)。每次完整执行是 \(\mathcal{O}(\log^2 x)\) 的。能否优化?
Trie 的本质就是不带左右端点的权值线段树。因此对于修改我们可以打上标记 lazyTag 代表修改,访问子节点时同步下放即可。优化后时间复杂度均摊 \(\mathcal{O}(\log x)\)。

\(k\ne 1\) 时 \(y\) 即为低 \(k-1\) 位与 \(x\) 相同的所有数,用主席树(可持久化线段树)的方式从根开始新建一棵树 \(q\),到达需要分裂的点时将其挂在树 \(q\) 上然后对树 \(q\) 做一个全局 \(+v\) 最后再把树 \(q\) 和原树合并。

注意实时维护节点的上传和下放。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
typedef unsigned int ui;
const int maxK = 32;
struct node
{
array<ui, 2> ch;
int sm;
ui lazyTag;
node() : ch{~0u, ~0u}, sm(0), lazyTag(0) {}
};
vector<node> t;
vector<ui> pool;
void delete_node(ui p)
{
t[p] = node();
pool.push_back(p);
}
ui new_node()
{
if (pool.size())
{
ui tmp = pool.back();
pool.pop_back();
return tmp;
}
t.emplace_back();
return t.size() - 1;
}
ui root = new_node();
void apply(ui p, ui v)
{
if (!~p) return;
t[p].lazyTag += v;
}
void pushdown(ui p)
{
if (!~p) return;
if (t[p].lazyTag & 1)
{
apply(t[p].ch[1], 1);
swap(t[p].ch[0], t[p].ch[1]);
}
apply(t[p].ch[0], t[p].lazyTag >> 1);
apply(t[p].ch[1], t[p].lazyTag >> 1);
t[p].lazyTag = 0;
}
void pushup(ui p)
{
if (!~p) return;
t[p].sm = (~t[p].ch[0] ? t[t[p].ch[0]].sm : 0) + (~t[p].ch[1] ? t[t[p].ch[1]].sm : 0);
}
void modify(ui x, int sm)
{
ui p = root;
for (ui d = 0; d < maxK; d++)
{
t[p].sm += sm;
pushdown(p);
ui c = x >> d & 1;
if (!~t[p].ch[c])
{
auto tmp = new_node();
t[p].ch[c] = tmp;
}
p = t[p].ch[c];
}
t[p].sm += sm;
}
ui merge(ui a, ui b)
{
if (!~a || !~b) return (~a ? a : b);
pushdown(a), pushdown(b);
t[a].sm += t[b].sm;
t[a].ch[0] = merge(t[a].ch[0], t[b].ch[0]);
t[a].ch[1] = merge(t[a].ch[1], t[b].ch[1]);
delete_node(b);
return a;
}
ui query(ui x)
{
ui p = root;
for (ui d = 0; d < maxK; d++)
{
pushdown(p);
ui c = x >> d & 1;
if (!~t[p].ch[c] || !t[t[p].ch[c]].sm) return d;
p = t[p].ch[c];
}
return maxK;
}
pair<ui, ui> split(ui p, ui x, ui k, ui d)
{
if (!~p) return {-1, -1};
if (d == k) return {-1, p};
pushdown(p);
ui q = new_node();
ui c = x >> d & 1;
auto tmp = split(t[p].ch[c], x, k, d + 1);
tie(t[p].ch[c], t[q].ch[c]) = tmp;
pushup(p), pushup(q);
return {p, q};
} // 分裂 first 返回原来的 second 返回分裂出来的
void update(ui x, ui k, ui v)
{
x &= ((1ull << k) - 1);
ui a, b;
tie(a, b) = split(root, x, k, 0);
// 注意这里是从根开始分裂
apply(b, v); // 打标记
root = merge(a, b);
}
int main()
{
int q;
cin >> q;
while (q--)
{
int opt;
ui x, k, v;
cin >> opt;
if (opt == 3)
{
cin >> x >> k >> v;
update(x, k, v);
}
else
{
cin >> x;
if (opt == 1) modify(x, 1);
else if (opt == 2) modify(x, -1);
else if (opt == 4) cout << (1ull << query(x)) << '\n';
// 这里注意用 64 位
}
}
return 0;
}
创作声明
本文遵循 CC BY-NC-SA 4.0 协议。
本文未使用 GenAI 工具辅助创作。
转载例如下:
[原文](https://www.luogu.com.cn/article/yr27lu6p)作者为 [clx201022](https://www.luogu.com.cn/user/552688),转载人保证会遵循 [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh-hans) 协议:以适当的方式署名,不以盈利为目的进行转载,并以相同方式共享。
浙公网安备 33010602011771号