字典树需要开的空间等于节点数,而不是需要插入的数的数量,搞错经常会wa、TLE

前言:
因为区间异或和不具有单调性,所以很难找到高效的算法去找到一个区间满足区间异或和不小于 且长度最短。区间问题的一个老套路是通过前缀和转化为两个点的问题,而且位运算有点难度的题都会涉及二进制,因为后面需要用到搜索,可以联想到字典树。

思路:

原数组转化为前缀和
我们可以枚举区间右端点,然后找到最近的一个下标满足,那么这个合法区间就是
我们可以按的二进制位去找下标
假设的第位是的第位是,在字典树中当前遍历到的节点是,字典树下一个需要访问的值是

如果,那么,如果此时,也就是说这个二进制位可以凑出,如果沿着节点继续搜,那么不管怎么搜都凑得到不小于的数
如果,那么
如果,也就是说不管取哪一个(第位已经确定),这个二进制位都不能凑出,就凑不到不小于的数,就没必要接着搜了

搜索完后把插入到字典树,同时把遍历的点标记,标记的值为,表示最近一次遍历这个节点的数的下标

code:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 7,maxm=3e6+10;
int f[maxn],top,n,k,pw[35];
int tir[maxm][2],pos[maxm];
bool s[35];
inline void add(int p) {
    int root=0;
    for(int i=30,id;i>=0;i--) {
        id=(f[p]&pw[i])!=0;
        if(tir[root][id]==0) {
            tir[root][id]=++top;
            tir[top][0]=tir[top][1]=0;
        }
        root=tir[root][id];
        pos[root]=p;
    }
}
inline int find(int p) {
    int root=0,res=-1;
    for(int i=30,id;i>=0;i--){
        id=(f[p]&pw[i])!=0;
        if(s[i]==0) {
            if(tir[root][id^1]) res=max(res,pos[tir[root][id^1]]);
        }
        else id^=1;
        if(tir[root][id]==0) return res;
        root=tir[root][id];
    }
    return max(res,pos[root]);
}
int main() {
    pw[0]=1;
    for(int i=1;i<=30;++i) pw[i]=pw[i-1]<<1;
    int T;
    scanf("%d",&T);
    while(T--) {
        int have=0;
        top=0;
        scanf("%d%d",&n,&k);
        for(int i=0;i<=30;++i) s[i]=(k&pw[i])!=0;
        for(int i=1;i<=n;++i) {
            scanf("%d",&f[i]);
            if(f[i]>=k&&!have) have=i;
            f[i]=f[i-1]^f[i];
        }
        if(have) {
            cout<<have<<' '<<have<<'\n';
            continue;
        }
        int l=-1,r=maxn,tmp;
        tir[0][0]=tir[0][1]=0; pos[0]=-1;
        add(0);
        for(int i=1;i<=n;++i) {
            tmp=find(i);
            if(~tmp&&(i-tmp)<(r-l)) 
                r=i,l=tmp;
            add(i);
        }
        if(~l) printf("%d %d\n",l+1,r);
        else printf("-1\n");
    }
    return 0;
}