可持久化 trie 树 (例题:AcWing 256. 最大异或和)

AcWing 256. 最大异或和

我们需要求的是

\[ans = \max_{l\ \le k\ \le r} (a[k] \oplus a[k + 1] \oplus a[k + 2] \oplus ... \oplus a[N] \oplus x) \]

\[s[i] = a[1] \oplus a[2] \oplus ... \oplus a[i] \]

容易看出当 \(i < j\) 时,

\[\begin{aligned} s[i] \oplus s[j] &= a[1] \oplus a[2] \oplus ... \oplus a[i] \oplus a[1] \oplus ... \oplus a[i] \oplus ... \oplus a[j] \\ &= a[i + 1] \oplus a[i + 2] \oplus ... \oplus a[j] \end{aligned} \]

所以我们可以替换为求

\[\begin{aligned} ans &= \max_{l\ \le k\ \le r} (a[k] \oplus a[k + 1] \oplus a[k + 2] \oplus ... \oplus a[N] \oplus x)\\ &= \max_{l\ \le k\ \le r} (s[k - 1] \oplus s[N] \oplus x) \end{aligned} \]

因此,我们仅需要保存每个点的异或前缀和,然后考虑使用一种可以保证我们检索时,可以将区间限制为 \(l \le k \le r\) 这个区间即可。这就是我们要使用的 可持久化 trie 树

代码如下:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <ctime>
using namespace std;
const int N = 6e5 + 10, M = N * 25;
int n, m, sum[N];
int tree[M][2], id[M], root[N], idx = 0;
// 由于初始数据有30w,新增数据也有30w,而且10 ^ 6 < 2 ^ 24
// 再加上根节点,也就是每个数最多需要25位

template<typename T>
void read(T& x)
{
    char a, b;
    for (a = 0; !isdigit(a); a = getchar()) b = a;
    for (x = 0; isdigit(a); a = getchar()) x = x * 10 - '0' + a;
    if (b == '-') x = -x;
}

void insert(int i, int k, int p, int q)
{
    if (k < 0) // 如果纪录结束了
    {
        id[q] = i; // 纪录当前节点所可以到达的最大范围 i 
        return ;
    }

    int v = sum[i] >> k & 1;
    if (p) tree[q][v ^ 1] = tree[p][v ^ 1];
    tree[q][v] = ++ idx;
    insert(i, k - 1, tree[p][v], tree[q][v]);
    id[q] = max(id[tree[q][0]], id[tree[q][1]]);
}

int query(int root, int c, int limit)
{
    // 选用合适的 root,就是第 r - 1 个节点作为 root 
    // 然后根据异或的前缀和性质才能保证在 r 左边
    int p = root;
    for (int i = 23; i >= 0; i --)
    {
        // c 是s[n] ^ x,从高位到低位逐位检索二进制上能跟 c 异或结果最大的数字
        int v = c >> i & 1;
        // 自带判空功能如果没有点, id 会为0, 那就肯定不能满足 >= l
        // 而 id 又同时可以限制当前的点是在 [l,r] 区间内
        // 另外,如果tree[p][v ^ 1]为空,那么tree[p][v]就肯定不为空,并在l r区间
        // 因为根据插入的代码, 每个节点至少有一条当前 s[i] 的完整路径
        // 而如果tree[p][v ^ 1]不为空但 id 小于 l, 同理也能选取到 tree[p][v]
        if (id[tree[p][v ^ 1]] >= limit) p = tree[p][v ^ 1];
        else p = tree[p][v];
    }
    return c ^ sum[id[p]];
}

int main()
{
#ifdef FIO
    freopen("E:/Code/In.in", "r", stdin);
    freopen("E:/Code/Out.out", "w", stdout);
#endif
    read(n), read(m);
    id[0] = -1;
    root[0] = ++ idx;
    insert(0, 23, 0, root[0]);

    for (int i = 1; i <= n; i ++)
    {
        int x;
        read(x);
        sum[i] = sum[i - 1] ^ x; // 前缀和序列
        root[i] = ++ idx;
        insert(i, 23, root[i - 1], root[i]);
    }

    char op[2];
    int l, r, x;
    while (m --)
    {
        scanf("%s", op);
        if (op[0] == 'A')
        {
            read(x);
            n ++;
            sum[n] = sum[n - 1] ^ x;
            root[n] = ++ idx;
            insert(n, 23, root[n - 1], root[n]);
        }
        else 
        {
            read(l), read(r), read(x);
            // 至少要包住第 r 个点,所以用 r - 1,否则会因为异或把 root[r] 抵消掉
            // l 同理
            printf("%d\n", query(root[r - 1], sum[n] ^ x, l - 1));
        }
    }
    return 0;
}
posted @ 2022-01-08 21:12  幼稚园茶茶子  阅读(24)  评论(0编辑  收藏  举报