题面:给定一个长度为n的序列,求一个异或不小于k的最小子序列,输出最小的左端点和其右端点。
解析:暴力方法是将序列的每一个点当作左端点,找到最小的子序列。复杂度o(n^2),显然是不行的。
我们知道a xor b= c;a xor b xor c= 0;若用sun[i]表示前i个数的异或值,则sum[i] xor sum[j]则表示区间[j,i]的异或值。问题就转变成找到两个sum,使他们的异或值大于等于k。
这里用01字典树来存n+1个sum,trie[i][0]=t,表示trie树上的i节点,有一条连出去的边,满足边上的字符标识是0,并且这条边的终点是x号节点。
代码

#include<bits/stdc++.h>
using namespace std;
const int M=3e6+10;
int trie[M][2];
int t,n,k;
int a[M];
int pos[M];//表示t节点在sum数组的位置。
int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&k);
        memset(a,0,sizeof(a));
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            a[i]=a[i]^a[i-1];//存前缀
        }


        int l=-1,r=n;
        int root=1;
        trie[1][0]=trie[1][1]=0;
        pos[1]=-1;//初始化

        for(int i=0;i<=n;i++)
        {
            int idx=1;    int ans=-1;
            for(int j=29;j>=0;j--){//k<2^30
                int now=(a[i]>>j)&1;//sum[i]第j位是0还是1
                if((k>>j)&1) idx=trie[idx][now^1];//当k的第j位为1时,两个数的异或值必须是1。
                else{
                    if(trie[idx][now^1]) ans=max(ans,pos[trie[idx][now^1]]);
//若为0,那么如果存在使得两个数的异或值为1的数,就更新位置,并进入子节点。
                    idx=trie[idx][now];
                }
                if(!idx) break;//若不满足条件则退出。
            }

            if(idx) ans=max(ans,pos[idx]);
            if(ans>=0&&(i-ans)<(r-l)) r=i,l=ans;//选更小的子序列。

            idx=1;
//将sum[i]存入trie中
            for(int j=29;j>=0;j--){
                int now=(a[i]>>j)&1;
                if(!trie[idx][now]) {
//若第idx个节点没边
                    trie[idx][now]=++root;
                    trie[root][0]=trie[root][1]=0;
                    pos[root]=-1;
                }
                    idx=trie[idx][now];
                pos[idx]=max(i,pos[idx]);
            }

        }    
            if(l>0) cout<<l+1<<" "<<r<<endl;//输出左右节点。
            else cout<<"-1"<<endl; 

    }


}