题面:给定一个长度为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; } }