clx201022

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

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

robotlife

接下来只剩操作三还没有考虑到。

先考虑 \(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)\)。

opt3

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

我不会取名

注意实时维护节点的上传和下放。

Accepted

点击查看代码
#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) 协议:以适当的方式署名,不以盈利为目的进行转载,并以相同方式共享。
posted on 2026-08-14 15:00  clx201022  阅读(29)  评论(0)    收藏  举报