题意:
给出一个整数序列(序列元素值 >= 0)和一个整数k(k>=0),求满足序列子段元素 异或和>=k 的最短子段(若有多个长度相同的最段子段输出最靠左的一个)
思路
利用 sum[i]^sum[j]==sum[i~j] 的性质,在该序列中从左往右查找,每次将当前位置的前缀和插入trie树,然后在trie树中找到与当前前缀和异或和>=k的最靠右的数,再更新答案
具体查找 L 的过程如下
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
int n, k, idx;
//pos[idx]表示第idx号节点在序列中代表的数字
int s[N], pos[N*31];
int tr[N*31][2];
void init() {
tr[0][0] = tr[0][1] = 0;
pos[0] = 0;
idx = 0;
}
void insert(int x, int r) {
int p = 0;
for (int i = 29; i >= 0; i--) {
int u = x>>i&1;
if (!tr[p][u]) {
tr[p][u] = ++idx;
//边插入数字边初始化
tr[idx][0] = tr[idx][1] = 0;
pos[idx] = 0;
}
p = tr[p][u];
//使pos[p]代表的点尽可能的靠右
//使得到的区间最短
pos[p] = max(pos[p], r);
}
}
int query(int x) {
int p = 0;
int res = -1;
for (int i = 29; i >= 0; i--) {
int p1 = x>>i&1;
int p2 = k>>i&1;
if (p2) {
//若k的第i为1
//那么两个数的异或值必须为1
//故下一个点找与当前点异或和为1的点
p = tr[p][p1^1];
} else {
//若k的第i为0
//若存在与当前点异或和为1的点则更新答案
//否则找下一个点找与当前点异或和为0的点
if (tr[p][p1^1])
res = max(res, pos[tr[p][p1^1]]);
p = tr[p][p1];
}
//若当前trie树上找不到满足条件的点,则结束本次查找
if (!p)
break;
}
if (p)
res = max(res, pos[p]);
return res;
}
void solve() {
init();
scanf("%d%d", &n, &k);
int l = -1, r = n;
for (int i = 1; i <= n; i++) {
int x;
scanf("%d", &x);
s[i] = s[i-1]^x;
}
for (int i = 1; i <= n; i++) {
insert(s[i], i);
//L的意义如图所示
int L = query(s[i]);
if (L!=-1 && i-L<r-l) {
l = L;
r = i;
}
}
if (l >= 0) {
cout << l+1 << ' ' << r << '\n';
} else {
puts("-1");
}
}
signed main() {
int t;
scanf("%d", &t);
while (t--) {
solve();
}
return 0;
}