题目链接:https://acm.hdu.edu.cn/showproblem.php?pid=6955
题目大意:给一个长度为n的序列,求一个区间[l,r]使a[l]^a[l+1]^...^a[r]>=k,输出满足条件最短的区间长度。
解题思路:(我是笨蛋看题解依然不会,感谢王同学的耐心讲解)
对于异或有以下性质:
如果a ^ b =c;那么 c ^ a = b 我们不难发现这个可以用前缀和来求
因此对于l ^ (l + 1)……^r =sun[r] ^ sum[l - 1]
如果我们要查询区间[l,r]的异或和>=k
其实只需要查询sum[r]^sum[l-1]>=k的l-r+1最小
枚举r,查找离r最近的l是什么 不断更新答案(保存更大的l)
但是这样时间复杂度是O(n^2) 数据范围是1e5肯定会TLE
因此可以建立01字典树,将查询时间优化到logn
代码实现:(细节在注释里)
#include <bits/stdc++.h>
using namespace std;
const int Mn = 1e5 + 6;
int tree[Mn * 31][2];
int mx[Mn * 31];
int k,cnt = 0;
void Add(int k,int r){//添加一个1-r的前缀异或为k的数值
int a = 0;
for(int i = 29;i >= 0;i --){
int base = k >> i;
base &= 1;//第i位(从低位数)
if(tree[a][base] == 0) tree[a][base] = ++cnt;//新建节点
a = tree[a][base];
mx[a] = max(mx[a],r);//相同值,选r更大,更靠右的,这个是更新l
}
}
//now是当前路径值
//bit是当前查询到第几位
//w是前缀的值
//a是节点编号
int Query(int now,int bit,int w,int a){
if(now >= k) return mx[a];//当前值大于k,满足条件直接返回
if(now + (1 << (bit + 1)) - 1 < k) return 0;
int a1,a2;
a1 = a2 = 0;//字典树返回的值
//提取出当前w的bit位和1异或
if(tree[a][1]) a1 = Query(now + ((w & (1 << bit)) ^ (1 << bit)),bit - 1,w,tree[a][1]);
if(tree[a][0]) a2 = Query(now + ((w & (1 << bit)) ^ (0 << bit)),bit - 1,w,tree[a][0]);
return max(a1,a2);
}
void Build(int a){
//初始化整棵树为0
if(tree[a][1]) Build(tree[a][1]);
if(tree[a][0]) Build(tree[a][0]);
tree[a][1] = tree[a][0] = 0;
}
void inint(){
Build(0);
for(int i = 0;i <= cnt;i ++) mx[i] = 0;
cnt = 0;
}
void Slove(){
int n;
inint();
scanf("%d%d",&n,&k);
int a,x;a = 0;
int l,r;
l = 0;
r = n + 1;//ansl,ansr
for(int i = 1;i <= n;i ++){
scanf("%d",&x);
a ^= x;
int res = Query(0,29,a,0);//返回的是当前节点异或某个前缀大于k的右侧值
if(res){
//如果sum[5] ^ sum[3] >= k 的话区间应该为4 ~ 5 即(3 ++) ~ 5(sum[r] ^ sum[l - 1])
res ++;//所以res++
if(i - res < r - l)//如果当前答案更优,那就更新区间l,r
l = res,r = i;
}
Add(a,i);
}
if(l) printf("%d %d\n",l,r);
else puts("-1");
}
int main(){
int _;scanf("%d",&_);
while(_--) Slove();
return 0;
}