题解: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)。

![](https://cdn.luogu.com.cn/upload/image_hosting/zh514ouc.png) 注意实时维护节点的上传和下放。 :::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) 协议:以适当的方式署名,不以盈利为目的进行转载,并以相同方式共享。 > ```