题解:P11160 機械生命体
clx201022
·
·
题解
博客园版本
题面传送门
操作一和操作二是平凡的,大多数数据结构都支持插入和删除。
题目中的 \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)。

注意实时维护节点的上传和下放。
:::success[[Accepted](https://www.luogu.com.cn/record/292837682)]
```cpp
#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](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh-hans) 协议。
本文未使用 GenAI 工具辅助创作。
转载例如下:
> ```py
> [原文](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) 协议:以适当的方式署名,不以盈利为目的进行转载,并以相同方式共享。
> ```