题面
输入样例
5
1
0
5
4
2
输出样例
6 4 5
题解
- 区间异或找最大,一看就是01字典树的变型,我们的板子点这里是找两个数的最大异或,此题是让找一个区间的异或和,那么我们就可以用前缀和的思想去做
- 用s数组表示前i个数的异或,s[l-1]^s[r] = al ^ al+1 ^ al+2 ^ … ^ar ,这样就可以求出任意一段连续区间的异或了
- 条件是让满足区间异或最大而且让r尽可能的小,那么我们就从左向右遍历,每次右端点增加一位(也就是区间长度+1),看在这个区间的最大异或是多少,如果大于就更新答案(等于是不更新的,因为要保证r尽可能的小)
- 还有一个条件是:如果仍然存在多个可选的序列,那么选择长度最短的那个序列。意思就是让 l 尽可能的大 ,我们只需要在插入的时候,将前面相同的值用后面的值覆盖掉就好,这样每次输出的一定是后面的值。
- 还没听懂?举个例子,假设s[3] =5 ,s[5]=5 ,s[7]=9, 假设在满足前两个条件下,按题中要求肯定是要s[7] ^ s[5] , 那么我们怎样避免s[3], 就是在插入的时候,我们会记录每个点的下标,对于 5 这个数,在字典树中插入的肯定是两次,从前到后依次插入,第一次插入5,记录下标为3,第二次插入5,发现已经存在,那么就不用新开一个节点,直接将5这个节点的下标改为5即可
代码
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;
const int M = N * 21;
int n, x;
int s[N], id[M];
int tr[M][2], idx;
void insert(int x, int k) {
int p = 0;
for (int i = 20; i >= 0; i--) {
int u = x >> i & 1;
if (!tr[p][u]) tr[p][u] = ++idx;
p = tr[p][u];
}
id[p] = k; //记录下标,覆盖操作
}
int query(int x) {
int p = 0;
for (int i = 20; i >= 0; i--) {
int u = x >> i & 1;
if (tr[p][u ^ 1]) p = tr[p][u ^ 1];
else p = tr[p][u];
}
return id[p];
}
int main() {
cin >> n;
insert(s[0], 0);
int res = -1;
int l, r;
for (int i = 1; i <= n; i++) {
cin >> x;
s[i] = s[i - 1] ^ x;
int k = query(s[i]);
int maxn = s[i] ^s[k];
if (maxn > res) {
res = maxn;
l = k + 1, r = i;
}
insert(s[i], i);
}
cout << res << " " << l << " " << r << endl;
return 0;
}