可持续化trie树

转自http://blog.csdn.net/BerryKanry/article/details/76165196

世界真的很大
trie树贪心求最大异或和大概也就是那么回事了
但是对于区间的查询就不是那么容易的了
考虑主席树的思想,怎么得到区间的值域的
这就是可持久化的trie树
说来容易
指针教做人哪
看题先:
description:

给定一个非负整数序列 {a},初始长度为 N。       
有   M个操作,有以下两种操作类型:

1 、A x:添加操作,表示在序列末尾添加一个数 x,序列的长度 N+12 、Q l r x:询问操作,你需要找到一个位置 p,满足 l<=p<=r,使得:

a[p] xor a[p+1] xor ... xor a[N] xor x 最大,输出最大是多少。 

    1
    2
    3
    4
    5
    6
    7

input:

第一行包含两个整数 N  ,M,含义如问题描述所示。   
第二行包含 N个非负整数,表示初始的序列 A 。 

接下来 M行,每行描述一个操作,格式如题面所述。   

    1
    2
    3
    4

output:

假设询问操作有 T个,则输出应该有 T行,每行一个整数表示询问的答案。

    1

首先考虑怎么把连续的异或和转化掉
考虑连续一段的数字之和的转化,除了线段树之外的高级东西,前(后)缀和这个东西在这种不修改的情况下就要优秀的多了
[L,R]的数字之和可以转化成sum[R]-sum[L-1]或last[L]-last[R+1],而考虑异或和这种东西,如果一个数连续异或偶数次就是0,而任何数异或0都是本身,除了0自己
所以如果处理一段连续的异或和的话,就可以考虑用异或前(后)缀和来处理了,而且由于有添加的操作,只会影响后缀而不会影响前缀,如果使用后缀的话每次修改都需要把前面的后缀全部修改一遍,这是不能接受的
既然能达到一样的区间处理效果,尽管每次求的是最大的后缀,但我们仍然选择用前缀维护
每次就用X^=sum[n],再用这样的X在L到R范围内找一个sum使其异或和最大
这就转化成了在一堆数里面选一个数与指定数异或和最大的经典trie树贪心的问题了
但我们不可能每次询问都对指定区间建一颗trie树,这样询问的代价就是O(n)的了,这是不能接受的。我们希望能够预处理出每次询问的trie树,而询问优势在线的,
可持久化trie树就应运而生了
考虑可持久化线段树,主席树的实现方式,将R的trie树减去L-1的trie树,类比主席树,得到的就应该是L到R区间的所有数得到的trie树,而这样的一颗颗trie树是可以预处理的,而每次在数列末尾加数字时再新建一颗trie树,就可以动态的维护这样的一颗颗树了
大概思想是有了,接下来考虑实现方法
还是要类比主席树
每一个点都建一颗trie树,存的是1到这个点的所有数的二进制的01串,而对于每一个点都重新建一颗这样的树,时间是O(n^2)的,不能接受的,而类比主席树,每一个点对应的树和前一颗树不同的地方只有一个数而已,完全没有必要重新建一棵树,这样空间时间上都能省一笔
可持久化trie树的大概思想好像差不多了
那么我们现在需要考虑的就只有具体的建树思路了
对于相邻的两棵树,只有一个数不同而已,一个数在trie上对应的就是一条链,建树时我们沿着这条链走,把这条链构造进新树里,其余结构全部指向旧树
就是说新树只有这一条链的部分是新建的,而其他部分全部与旧树共享,类比主席树
还剩下查询操作,关键是判断在当前区间内有没有trie树的这条链,考虑主席树是怎么做的,对每一个节点开一个域cnt,记录这个节点以下有几个数,每次按trie贪心的方法查询时,判断R树的节点cnt值减去L-1树的节点的cnt值为不为0,不为0就说明区间内有这么一个数可以走
类比主席树
大概想一下,具体化一下具体的代码就可以开始写的,还是挺好写的,只能说指针教做人哪
学新东西时还是注意一下细节
首先是指针写法,防RE的办法就不说了,很简单,注意trie树insert时使用的是递归还是while,是有区别的,while需要另设一个指针,不能直接用root跳
还有就是边界问题,一定要在trie树里一开始插入一个0,这很重要,因为如果取1到n为值的话,应该是sum[n]^sum[0],如果trie树里没有0的话,就无法实现这一步操作,我的做法是直接多建一棵树,往里面插值0,然后将n棵树依次往后推一位,这样第一棵树的值就是0了,每次查的时候就查L-1到R就行了,不然应该是L-2到R-1才对(这个也很重要,因为L到R选择一个后缀,是指用1到X的异或和异或上sum[L-1]到sum[R-1],而想要得到包含sum[L-1]和sum[R-1],不包含sum[R] 的trie树,就要用第R-1颗树减去L-2颗树,而我们在之前多建了一个点,所以查R到L-1就行了)
完整代码:

#include<stdio.h>
#include<cstdio>
#include<algorithm>
using namespace std;

struct node
{
    int cnt;
    node *ch[2];
}pool[20000010],*tail=pool,*root[600010],*null;

int n,m,sum[600010];
char ss[10];

node *newnode()
{
    node *nd=++tail;
    nd->cnt=0;
    nd->ch[0]=nd->ch[1]=null;
    return nd;
}

void init()
{
    null=++tail;
    null->cnt=0;
    null->ch[0]=null->ch[1]=null;
}

void insert(node *ne,node *&nd,int x)
{
    nd=newnode();
    node *now=nd;
    for(int i=24;i>=0;i--)
    {
        now->ch[0]=ne->ch[0],now->ch[1]=ne->ch[1];
        now->cnt=ne->cnt+1;
        int idx=(x&(1<<i))>0;
        now->ch[idx]=newnode();
        now=now->ch[idx];ne=ne->ch[idx];
    }
    now->cnt=ne->cnt+1;
}

int query(node *ne,node *nd,int x)
{
    int num=0;
    for(int i=24;i>=0;i--)
    {
        int idx=(x&(1<<i))>0;
        if(nd->ch[!idx]->cnt-ne->ch[!idx]->cnt)
        {
            num|=(1<<i);
            ne=ne->ch[!idx],nd=nd->ch[!idx];
        }
        else
            ne=ne->ch[idx],nd=nd->ch[idx];
    }
    return num;
}
int main()
{
    init();
    scanf("%d%d",&n,&m);
    n++;
    for(int i=2;i<=n;i++)
    {
        scanf("%d",&sum[i]);
        sum[i]^=sum[i-1];
    }
    root[0]=newnode();
    for(int i=1;i<=n;i++)
        insert(root[i-1],root[i],sum[i]);   
    while(m--)
    {
        scanf("%s",ss);
        if(ss[0]=='A')
        {
            scanf("%d",&sum[++n]);
            sum[n]^=sum[n-1];
            insert(root[n-1],root[n],sum[n]);
        }
        else
        {
            int L,R,X;
            scanf("%d%d%d",&L,&R,&X);
            X^=sum[n];
            printf("%d\n",query(root[L-1],root[R],X));
        }   
    }   
    return 0;
}
/*
Whoso pulleth out this sword from this stone and anvil is duly born King of all England
*/
    原文作者:Trie树
    原文地址: https://blog.csdn.net/qq_36799943/article/details/79517039
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞