[BZOJ2741][FOTILE模拟赛]L-可持久化字典树-分块

L

Description

FOTILE得到了一个长为N的序列A,为了拯救地球,他希望知道某些区间内的最大的连续XOR和。
即对于一个询问,你需要求出max(Ai xor Ai+1 xor Ai+2 … xor Aj),其中l<=i<=j<=r。
为了体现在线操作,对于一个询问(x,y):
l = min ( ((x+lastans) mod N)+1 , ((y+lastans) mod N)+1 ).
r = max ( ((x+lastans) mod N)+1 , ((y+lastans) mod N)+1 ).
其中lastans是上次询问的答案,一开始为0。

Input

第一行两个整数N和M。
第二行有N个正整数,其中第i个数为Ai,有多余空格。
后M行每行两个数x,y表示一对询问。

Output

共M行,第i行一个正整数表示第i个询问的结果。

Sample Input

3 3
1 4 3
0 1
0 1
4 3

Sample Output

5
7
7

HINT

N=12000,M=6000,x,y,Ai在signed longint范围内。

Source

By seter


虽然每个题解都有写,但是

这题的x+lastans、y+lastans可能会在signed longint范围之外

因为咱一开始没有看见直到心血来潮点开讨论


思路:
因为区间很麻烦,所以考虑记录前缀异或和,问题变成在一段区间内,两两数之间的最大异或值。

对于这种最大异或值就要想到trie。
然后因为区间询问,所以trie需要可持久化。
那么得到一个 O(nm) 的暴力~

考虑优化:分块。
对于每个块,预处理块的起点到块起点后面的每个位置的询问答案。
对于每个询问,首先用最近的块左端点更新答案,然后剩下部分暴力
复杂度 O(nm) ,可过~

这道题可以给人一个教训:当你实在改不出来时,记得看讨论……

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>

using namespace std;

inline int read()
{
    int x=0;char ch=getchar();
    while(ch<'0' || '9'<ch)ch=getchar();
    while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
    return x;
}

typedef long long ll;

const int N=12009;
const int M=N*45;
const int K=31;

int n,m,ans;
int a[N],s[N],sum[119][N];
int blk,bel[N],fst[N];
int rt[N],ch[M][2],val[M],tot;

inline void cp(int x,int y)
{
    val[y]=val[x];
    ch[y][0]=ch[x][0];
    ch[y][1]=ch[x][1];
}

inline int insert(int pre,int dep,int x)
{
    int now=++tot;
    cp(pre,now);
    val[now]++;

    if(!dep)return now;
    int nxt=(x>>(dep-1))&1;
    ch[now][nxt]=insert(ch[pre][nxt],dep-1,x);
    return now;
}

inline int query(int tl,int tr,int x)
{
    int ret=0;
    for(int i=K-1;i>=0;i--)
    {
        int nxt=(x>>i&1)^1;
        if(val[ch[tr][nxt]]-val[ch[tl][nxt]]>0)
            ret|=1<<i,tl=ch[tl][nxt],tr=ch[tr][nxt];
        else
            tl=ch[tl][nxt^1],tr=ch[tr][nxt^1];
    }
    return ret;
}

int main()
{
    n=read(),m=read();  
    rt[0]=insert(0,K,s[0]);
    for(int i=1;i<=n;i++)
        rt[i]=insert(rt[i-1],K,s[i]=s[i-1]^(a[i]=read()));

    blk=sqrt(n);
    fst[bel[1]=0]=1;
    for(int i=2;i<=n;i++)
    {
        bel[i]=i/blk;
        if(bel[i]!=bel[i-1])
            fst[bel[i]]=i;
    }

    for(int i=0;fst[i];i++)
        for(int j=fst[i]+1;j<=n;j++)
            sum[i][j]=max(sum[i][j-1],query(rt[fst[i]-1],rt[j-1],s[j]));

    while(m--)
    {
        int l=((ll)read()+(ll)ans)%(ll)n+1;
        int r=((ll)read()+(ll)ans)%(ll)n+1;
        if(l>r)swap(l,r);

        int p=l/blk+1;
        ans=sum[p][r];
        for(int i=min(p*blk-1,r-1);i>=l-1;i--)
            ans=max(ans,query(rt[i],rt[r],s[i]));
        printf("%d\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值