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

wuchangjian2021-11-04 10:37:15编程学习

题意在这里插入图片描述

思路

  1. 这题的查询操作是,我们要在 [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;
}

相关文章

深度学习核心技术精讲100篇(七十九)-深度学习应用实战案例:携程金融自动化迭代反欺诈模型体系

前言 支付欺诈风险是携程金融风控团队的主要防控对象,它一般是指用户卡片信息或账号信息泄...

【优化求解】基于无序灰狼算法求解多目标问题matlab代码

【优化求解】基于无序灰狼算法求解多目标问题matlab代码

1 简介 Mirjalili 等人提出了一种新的群体智能算法———灰狼优化算法(GWO...

怎么样可以给你的网站套上Cloudflare

文章目录 Cloudflare 是什么准备工作开搞 Cloudfla...

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。