P4735 最大异或和(可持久化trie树、求最大区间异或和)

P4735 最大异或和
P4735 最大异或和(可持久化trie树、求最大区间异或和)_第1张图片

我们维护一个前缀异或和: s [ i ] = a [ 1 ]   x o r   a [ 2 ]   x o r   … a [ i − 1 ]   x o r   a [ i ] s[i] = a[1] \ xor\ a[2]\ xor\ … a[i-1] \ xor\ a[i] s[i]=a[1] xor a[2] xor a[i1] xor a[i]

a [ p ]   x o r   a [ p + 1 ]   x o r   …   x o r   a [ N ]   x o r   x a[p]\ xor\ a[p+1]\ xor\ …\ xor\ a[N]\ xor\ x a[p] xor a[p+1] xor  xor a[N] xor x 就相当于 s [ N ]   x o r   x   x o r   s [ p − 1 ] s[N] \ xor\ x\ xor\ s[p-1] s[N] xor x xor s[p1]

这样 S [ N ]   x o r   x S[N] \ xor\ x S[N] xor x就是一个定值 v a l val val,则相当于求一个 p p p 满足 l − 1 < = p − 1 < = r − 1 l-1 <= p-1 <= r-1 l1<=p1<=r1 使得 v a l   x o r   s [ p ] val \ xor\ s[p] val xor s[p] 值最大。

因为是异或运算,我们可以利用 “最大异或对” 这道题的一些性质,使用 t r i e trie trie 树。我们维护前缀异或和,找到一个 p p p ,贪心地使得 s [ p ] s[p] s[p]的二进制最高位开始到最低位的每一个数字都尽量与 v a l val val 的二进制对应位相反(相同为0不同为1)
前缀异或和的每个版本都可以用持久化的 t r i e trie trie 记录下来。
对持久化 t r i e trie trie 的每个节点额外维护一个信息 m a x _ i d max\_id max_id ,表示其所属的最大的持久化版本,显然一个节点的 m a x _ i d max\_id max_id 等于其子节点中最大的版本号(因为子节点要么是连向之前的版本,要么创建了该节点后再创建子节点)。

因为要满足边界性所以要用可持久化trie树的保存历史版本来界定边界

  • 边界 [ l − 1 , r − 1 ] [l-1, r-1] [l1,r1] 判断:

如果一个节点的 m a x _ i d max\_id max_id 小于 l − 1 l-1 l1 ,说明这个节点是 s [ l − 1 ] s[l-1] s[l1] 插入之前就已经创建出来的节点,不应该考虑在内。

对持久化树的某一个版本的根节点开始往下访问,所能访问到的节点的版本不会超过该根节点的版本,所以该题只需要从 r − 1 r-1 r1 版本开始访问即可满足取到的 p p p 小于等于 r − 1 r-1 r1

//trie树维护的是前缀异或和的二进制01串
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int N = 500007, M = N * 25;
/*
templateinline T read(T &x)
{
    x=0;ll f=1;char c;
    while(!isdigit(c=getchar()))if(c=='-')f=-1;
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return x*f;
}
*/
int n, m;
int sum[N];
int tr[M][2], max_id[M];
int root[N], idx;
//当前的区间位置(到时候[L,R]对比的就是这里的i)k表示当前是第几位,一共23位2^23,越高越大
void insert(int i, int k, int p, int q){//p上一个版本q下一个版本
    if(k < 0){
        max_id[q] = i;
        return ;
    }
    int v = sum[i] >> k & 1;
    if(p)//如果上个版本的当前结点是有东西的就继承一下
        tr[q][v ^ 1] = tr[p][v ^ 1];
    tr[q][v] = ++ idx;
    insert(i, k - 1, tr[p][v], tr[q][v]);
    max_id[q] = max(max_id[tr[q][0]], max_id[tr[q][1]]);
}

int query(int root, int C, int L){
    int p = root;
    for(int i = 23;i >= 0;i -- ){
        int v = C >> i & 1;
        //如果版本小于L-1,说明这个节点是s[l-1]插入之前就已经创建出来的节点,不应该考虑在内
        if(max_id[tr[p][v ^ 1]] >= L)//如果与当前这一位相反的数存在并且该数的位置在范围以内(求异或相反为1更大)
            p = tr[p][v ^ 1];
        else p = tr[p][v];
    }
    return C ^ sum[max_id[p]];
}

int main(){
    scanf("%d%d", &n, &m);
    max_id[0] = -1;//因为id从0开始这里要取一个更小的
    root[0] = ++ idx;
    insert(0, 23, 0, root[0]);

    for(int i = 1;i <= n; ++ i){
        int x;
        scanf("%d", &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 == 'A'){
            scanf("%d", &x);
            n ++ ;
            sum[n] = sum[n - 1] ^ x;
            root[n] = ++ idx;
            insert(n, 23, root[n - 1], root[n]);
        }
        else {
            scanf("%d%d%d", &l, &r, &x);
            //l<=p<=r,所以要查询r-1版本的
            printf("%d\n", query(root[r - 1], sum[n] ^ x, l - 1));
        }
    }
}

你可能感兴趣的:(可持久化数据结构,#,Trie树)