AcWing 256:最大异或和 ← 可持久化 Trie

【题目来源】
https://www.acwing.com/problem/content/258/
https://www.luogu.com.cn/problem/P4735

【题目描述】
给定一个非负整数序列 a,初始长度为 N。
有 M 个操作,有以下两种操作类型:
1. A x:添加操作,表示在序列末尾添加一个数 x,序列的长度 N 增大 1。
2. Q l r x:询问操作,你需要找到一个位置 p,满足 l≤p≤r,使得:a[p] xor a[p+1] xor ... xor a[N] xor x 最大,输出这个最大值。

【输入格式】
第一行包含两个整数 N,M,含义如问题描述所示。
第二行包含 N 个非负整数,表示初始的序列 A。
接下来 M 行,每行描述一个操作,格式如题面所述。

【输出格式】
每个询问操作输出一个整数,表示询问的答案。
每个答案占一行。

【输入样例】
5 5
2 6 4 3 6
A 1 
Q 3 5 4 
A 4 
Q 5 7 0 
Q 3 6 6

【输出样例】
4
5
6

【数据范围】
1≤N,M≤3×10^5,0≤a[i]≤10^7。

【算法分析】
可持久化 Trie 是求解「动态 / 区间最大异或和」问题的标准首选解法,尤其是面对「静态区间最大异或」场景时,它的效率和简洁性都远优于其他方法(比如暴力、线段树套其他结构)。

● 可持久化是数据结构的一种设计思想,核心是对数据结构进行修改(插入、更新、删除)时,保留其所有历史版本,且各个版本独立可用,同时不同版本间共享未修改的公共部分以节省内存。

● 可持久化 Trie 与可持久化线段树的核心思想完全一致,本质都是「增量更新、共享不变结点、版本独立完整」。掌握其中一种可持久化数据结构的实现逻辑,都很容易迁移理解另一种。

● ​​​​​​​可持久化 Trie,专注于字符串 / 二进制型数据,处理的是「匹配 / 异或」问题(前缀匹配、历史版本字符串查询、区间异或最值);可持久化线段树,专注于数值型数据(整数、浮点数),处理的是「区间统计」问题(区间和、区间最值、区间第 k 大)。

● 前缀异或和:https://blog.csdn.net/hnjzsyjyj/article/details/154304346
(1)前缀异或和是一种基于异或运算的前缀和技术,用于高效处理子区间异或相关的问题。前缀异或和 s 定义为:s[i] = a[1] ⊕ ... ⊕ a[i],其中 ⊕ 表示异或运算。特别地,s[0] = 0。
(2)前缀异或和重要性质
求证:若定义前缀异或和 s[i]=a[1]⊕a[2]⊕⋯⊕a[i],则子区间 [le,ri] 的异或和等于 s[ri]⊕s[le−1]。
证明:由前缀异或和的定义,知:s[ri] = a[1]⊕a[2]⊕⋯⊕a[ri],s[le−1] = a[1]⊕a[2]⊕⋯⊕a[le−1]。
又已知 区间 [le,ri] 的异或和等于 a[le]⊕⋯⊕a[ri]。
所以 s[ri]⊕s[le−1] = (a[1]⊕⋯⊕a[ri]) ⊕ (a[1]⊕⋯⊕a[le−1])
= (a[1]⊕⋯⊕a[le−1]) ⊕ (a[1]⊕⋯⊕a[le−1]⊕a[le]⊕⋯⊕a[ri])
= (a[1]⊕⋯⊕a[le−1]) ⊕ (a[1]⊕⋯⊕a[le−1]) ⊕ (a[le]⊕⋯⊕a[ri])
= 0 ⊕ (a[le]⊕⋯⊕a[ri])
= a[le]⊕⋯⊕a[ri]
综上,得证。

● AcWing 143.最大异或对:https://blog.csdn.net/hnjzsyjyj/article/details/155641820
“AcWing 256:最大异或和”,是“AcWing 143:最大异或对”的加强版。

【算法代码:注释版

#include <bits/stdc++.h>
using namespace std;

const int MAX_LEN=6e5+5; //Maximum sequence length (initial N + M)
const int BITS=25; //10^7 has 24 binary bits (2^24=16,777,216) at most
const int MAX_NODE=MAX_LEN*BITS;

struct TrieNode { //Persistent 0-1 Trie
    int cnt;
    int ch[2]; //children
} trie[MAX_LEN*BITS];

int root[MAX_LEN]; //root node of every version
int node_cnt=0; //Node counter
int prefix_xor[MAX_LEN]; //prefix_xor[0]=0, prefix_xor[i]=a[1]^...^a[i]

/*Insert a number into the trie,
creating a new version based on previous version pre*/
int insert(int pre,int num) {
    int cur=++node_cnt;
    int now=cur;
    trie[now]=trie[pre];
    trie[now].cnt++;

    //Traverse from the highest bit to the lowest bit (23 to 0)
    for(int i=BITS-1; i>=0; i--) {
        int bit=(num>>i) & 1; //Get current bit's value
        int pre_child=trie[pre].ch[bit];
        trie[now].ch[bit]=++node_cnt; //Create a new child node
        now=trie[now].ch[bit]; //Move to the next-level node
        pre=pre_child;
        trie[now]=trie[pre];
        trie[now].cnt++;
    }
    return cur;
}

//Query the maximum value of XOR with target in the interval [L, R]
int query(int L,int R,int target) {
    int res=0;
    int node_L=root[L-1];
    int node_R=root[R]; //version at right boundary
    for(int i=BITS-1; i>=0; i--) {
        int bit=(target>>i) & 1;
        int opp_bit=!bit; //opposite_bit

        // Check whether the opposite bit exists in the interval [L,R]
        if(trie[trie[node_R].ch[opp_bit]].cnt-trie[trie[node_L].ch[opp_bit]].cnt>0) {
            res|=(1<<i);
            node_L=trie[node_L].ch[opp_bit];
            node_R=trie[node_R].ch[opp_bit];
        } else { //The opposite bit does not exist, choose the same bit
            node_L=trie[node_L].ch[bit];
            node_R=trie[node_R].ch[bit];
        }
    }
    return res;
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    int n,m;
    cin>>n>>m;
    prefix_xor[0]=0;
    for(int i=1; i<=n; i++) {
        int x;
        cin>>x;
        prefix_xor[i]=prefix_xor[i-1]^x;
    }

    root[0]=0;
    for(int i=1; i<=n; i++) {
        root[i]=insert(root[i-1],prefix_xor[i-1]);
    }

    int cur_len=n;
    while(m--) {
        char op[2];
        cin>>op;
        if(op[0]=='A') { //Append x to the end of the sequence
            int x;
            cin>>x;
            cur_len++;
            prefix_xor[cur_len]=prefix_xor[cur_len-1]^x;
            root[cur_len]=insert(root[cur_len-1],prefix_xor[cur_len-1]);
        } else if(op[0]=='Q') {
            int le,ri,x;
            cin>>le>>ri>>x;
            int target=prefix_xor[cur_len]^x;
            int ans=query(le,ri,target);
            cout<<ans<<'\n';
        }
    }

    return 0;
}

/*
in:
5 5
2 6 4 3 6
A 1
Q 3 5 4
A 4
Q 5 7 0
Q 3 6 6

out:
4
5
6
*/

【算法代码:无注释版

#include <bits/stdc++.h>
using namespace std;

const int MAX_LEN=6e5+5;
const int BITS=25;
const int MAX_NODE=MAX_LEN*BITS;

struct TrieNode {
    int cnt;
    int ch[2];
} trie[MAX_LEN*BITS];

int root[MAX_LEN];
int node_cnt=0;
int prefix_xor[MAX_LEN];

int insert(int pre,int num) {
    int cur=++node_cnt;
    int now=cur;
    trie[now]=trie[pre];
    trie[now].cnt++;

    for(int i=BITS-1; i>=0; i--) {
        int bit=(num>>i) & 1;
        int pre_child=trie[pre].ch[bit];
        trie[now].ch[bit]=++node_cnt;
        now=trie[now].ch[bit];
        pre=pre_child;
        trie[now]=trie[pre];
        trie[now].cnt++;
    }
    return cur;
}

int query(int L,int R,int target) {
    int res=0;
    int le=root[L-1];
    int ri=root[R];
    for(int i=BITS-1; i>=0; i--) {
        int bit=(target>>i) & 1;
        int opp=!bit;

        if(trie[trie[ri].ch[opp]].cnt-trie[trie[le].ch[opp]].cnt>0) {
            res|=(1<<i);
            le=trie[le].ch[opp];
            ri=trie[ri].ch[opp];
        } else {
            le=trie[le].ch[bit];
            ri=trie[ri].ch[bit];
        }
    }
    return res;
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    int n,m;
    cin>>n>>m;
    prefix_xor[0]=0;
    for(int i=1; i<=n; i++) {
        int x;
        cin>>x;
        prefix_xor[i]=prefix_xor[i-1]^x;
    }

    root[0]=0;
    for(int i=1; i<=n; i++) {
        root[i]=insert(root[i-1],prefix_xor[i-1]);
    }

    int cur_len=n;
    while(m--) {
        char op[2];
        cin>>op;
        if(op[0]=='A') {
            int x;
            cin>>x;
            cur_len++;
            prefix_xor[cur_len]=prefix_xor[cur_len-1]^x;
            root[cur_len]=insert(root[cur_len-1],prefix_xor[cur_len-1]);
        } else if(op[0]=='Q') {
            int le,ri,x;
            cin>>le>>ri>>x;
            int target=prefix_xor[cur_len]^x;
            int ans=query(le,ri,target);
            cout<<ans<<'\n';
        }
    }

    return 0;
}

/*
in:
5 5
2 6 4 3 6
A 1
Q 3 5 4
A 4
Q 5 7 0
Q 3 6 6

out:
4
5
6
*/




【参考文献】
https://oi-wiki.org/ds/persistent-trie/
https://blog.csdn.net/hnjzsyjyj/article/details/154304346
https://blog.csdn.net/hnjzsyjyj/article/details/154517364
https://blog.csdn.net/hnjzsyjyj/article/details/155641820
https://www.acwing.com/solution/content/51419/
https://www.cnblogs.com/Anchor1201/p/14690219.html
​​​​​​​https://www.luogu.com.cn/problem/P4585
https://www.acwing.com/solution/content/161615/
https://oi-wiki.org/string/trie/#%E7%BB%B4%E6%8A%A4%E5%BC%82%E6%88%96%E6%9E%81%E5%80%BC

posted @ 2026-02-04 08:38  Triwa  阅读(0)  评论(0)    收藏  举报