Xor sum HDU - 6955
题意:
给定一个长度为n的整数序列,求其XOR和不小于k的最短连续子序列。
如果有多个相同长度的连续子序列,则打印具有最小左端点的连续子序列。
如果没有连续的子序列开关XOR总和不小于k,只需打印“-1”。
题解:
有关01串且求异或值相关内容,一般都用01字典树,本题也不例外
本题如何维护字典树呢?
我们先求出所有的前缀异或和[1,P],当前在Q的位置,我们寻找一个离Q最近的一个数,使得Q ^ P>=K。每次查询时,(此时字典树中只插入了Q之前的所有前缀异或和),都会从字典数的高位枚举到低位,如果K的第J位是1,为了保证Q ^ P>=K,Q ^ P的值也必须是1,那么P和Q的第J位就要不同,也就是我们要向Q的第J位相反的方向寻找,即 c = (Q>>j)&1寻找nxt[now][c ^ 1]。
如果K的第J位是0,此时Q^P的第J位可以是0或1,如果是1后面就不用比较了,已经大于K了,所以如果有1,即nxt[now][c ^ 1]存在,我们就记录最大的flag[],然后我们要向net[now][c]的方向寻找。为什么?先讲下flag[]表示的该异或值出现位置的最后的一次位置,也就是flag的值会尽可能靠近Q,这样才会让区间最小。我们在插入时维护flag,使得每个点都有最大的flag,当上面说的nxt[now][c ^ 1],此时K的第J位是0,我们不用再去走1这条路,因为已经比K大了,后面的也一定大于K,所以取最大flag即可,但是如果net[now][c]还是要继续走,因为目前还不知道是否大于等于K
(讲的有些乱,可以看看代码,代码很清晰)
代码:
#include<bits/stdc++.h>
#define debug(a,b) printf("%s = %d\n",a,b);
typedef long long ll;
using namespace std;
//qdu打铁匠
const ll INF=0x3f3f3f3f;
inline int read(){int s=0,w=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();//s=(s<<3)+(s<<1)+(ch^48);return s*w;
}
const int maxn=1e7;
ll nex[maxn][3], cnt=0;
ll flag[210000000];
ll k;
struct trie
{void insert(ll x,ll pos){int p =0;for(int i=29; i>=0; i--){ll c=((x>>i)&1);if (!nex[p][c]){nex[p][c] = ++cnt;flag[cnt]=-1;nex[cnt][1]=nex[cnt][0]=0;} // 如果没有,就添加结点p = nex[p][c];flag[p]=max(flag[p],pos);}}ll find(ll x){ll ma=-1;ll p=0;for(int i=29; i>=0; i--){ll c=((x>>i)&1);ll ok=((k>>i)&1);if(ok==0){if(nex[p][c^1])ma=max(ma,flag[nex[p][c^1]]);p=nex[p][c];}else{p=nex[p][c^1];}if(p==0)return ma;}return max(ma,flag[p]);}
} tree;
ll a[1111111];
signed main()
{ll t;t=read();while(t--){ll n;n=read();k=read();for(int i=1; i<=n; i++){a[i]=read();a[i]^=a[i-1];}for(int i=0; i<=cnt; i++){nex[i][0]=0;nex[i][1]=0;flag[i]=0;}cnt=0;ll ansl=0,ansr=n;ll ma;for(int i=1; i<=n; i++){ma=-1;ll x=tree.find(a[i]);if(x!=-1){ma=max(x,ma);}if(i-ma<ansr-ansl&&ma>0){ansl=ma;ansr=i;}tree.insert(a[i],i);}if(ansl>0){printf("%d %d\n",ansl+1,ansr);}else{printf("-1\n");}}
}