acwing 256. 最大异或和(可持久化 trie 树)

题意在这里插入图片描述

思路

  1. 当我们选择一个合适 p 之后,那么答案的最大值就是:ans=a [p] ^ a [p+1]^ . . .a [r]…^a [n], 我们用 sum [i] 表示前缀异或和, 那么 ans=sum [n] ^ sum [p-1] , 而这样题目就转化为了在区间 [l, r] 中选择一个数 p,使 sum [p-1] ^ sum [n] 的值最大,而 sum [n] 的值是已知的,我们可以用 trie 数去解决找 p 的这个过程,
  2. 这题的查询操作是,我们要在 [l, r] 中选择一个 p,这个 p 是有限制的,我们可以利用可持久化 trie 树来保留历史版本,这样我们我们 l~r 之间的历史版本进行选择。

代码

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
// 30w初始数据和30w新增,  而10的7次方小于2的24次方, 再加上根节点, 就是说每个数最多需要25位;
const int N = 600010, M = N * 25;

int n, m;
int s[N]; // 前缀和序列
int tr[M][2];
int max_id[M];  // 用于记录当前根节点版本的最大id范围
int root[N], idx;

// i是第i个插入的数的i, p是上一个插入的数的节点号, q是当前节点号, k是现在取到第k位
void insert(int i, int k, int p, int q)
{
    // 如果记录结束了
    if (k < 0)
    {
        max_id[q] = i;  // 记录当前节点(可能会被后面公用)所能到达的最大范围i
        return;
    }

    // 取出前缀和的二进制表示从右往左数第k位v
    // 需要注意的是, 这个s[i]就是我们要存的东西!!!!!
    int v = s[i] >> k & 1;

    // 如果前一个节点存在当前节点没有的分支, 那就把当前节点的这个空的路径指过去, 这就相当于复制!
    if (p) tr[q][v ^ 1] = tr[p][v ^ 1];

    tr[q][v] = ++idx; // 现在才是正常trie树插入

    // 递归插入下一位二进制, tr[q][v]就是我们本轮插入的新节点
    // 而前面我们只复制了前一轮的不同v方向的路径, v方向的还没动过, 于是放到p里面等下一轮
    // 至于为什么可以放到下一轮, 因为当前q新插入的数字(二进制当前位)是v, 而p的这条路径也是v
    // 所以暂时不需要复制
    insert(i, k - 1, tr[p][v], tr[q][v]);

    // 下面是递归到所有点都插入完成才开始进行的, 所以能把最大max_id递归传递回去
    // 每个点的最大范围用子节点最大的值, 然后还能递归传递回去, 因为当前递归层
    // 的q, 就是上一层的tr[q][v], 观察易知每个节点都会有对应max_id
    max_id[q] = max(max_id[tr[q][0]], max_id[tr[q][1]]);
}

int query(int r, int C, int l)
{
    // 选用合适的root, 就是第r-1个节点作为root(-1已在传参前完成)
    // 然后根据异或的前缀和性质才能保证在r左边
    int p = root[r];

    for (int i = 23; i >= 0; i--)
    {
        // C是s[n] ^ x, 从高位到低位逐位检索二进制每一位上能跟C异或结果最大的数
        int v = C >> i & 1; 
        // 自带判空功能如果没有点, max_id会为0, 那就肯定不能满足>=l
        // 而max_id又同时可以限制当前的点是在l r区间内
        // 另外, 如果tr[p][v^1]为空, 那么tr[p][v]就肯定不为空,并在l r区间, 因为根据
        // 插入的代码, 每个节点至少有一条当前s[i]的完整路径
        // 而如果tr[p][v^1]不为空但maxid小于l, 同理也能选取到tr[p][v]
        if (max_id[tr[p][v ^ 1]] >= l) p = tr[p][v ^ 1]; 
        else p = tr[p][v];
    }

    return C ^ s[max_id[p]];
}

int main()
{
    scanf("%d%d", &n, &m);
    // 前缀和, 初始化root[0]
    max_id[0] = -1;
    root[0] = ++idx;
    insert(0, 23, 0, root[0]);
    for (int i = 1; i <= n; i++)
    {
        int x;
        scanf("%d", &x);
        s[i] = s[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')
        {
            scanf("%d", &x);
            n++;
            s[n] = s[n - 1] ^ x;
            root[n] = ++idx;
            insert(n, 23, root[n - 1], root[n]);
        }
        else
        {
            scanf("%d%d%d", &l, &r, &x);
            // 至少要包住第r个点, 所以用r-1, 否则会因为异或把root[r]抵消掉
            // l也同理
            printf("%d\n", query(r - 1, s[n] ^ x, l - 1));
        }
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值