链接:https://ac.nowcoder.com/acm/problem/22998
来源:牛客网
题号:NC22998
时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 32768K,其他语言65536K
64bit IO Format: %lld
题目描述
农民约翰在喂奶牛的时候被另一个问题卡住了。他的所有N(1 <= N <= 100,000)个奶牛在他面前排成一行(按序号1..N的顺序),按照它们的社会等级排序。奶牛#1有最高的社会等级,奶牛#N最低。每个奶牛同时被指定了一个不唯一的附加值,这个数在 [0, 221 - 1] 的范围内。
帮助农民约翰找出应该从哪一头奶牛开始喂,使得从这头奶牛开始的一个连续的子序列上,奶牛的附加值的异或最大。
如果有多个这样的子序列,选择结尾的奶牛社会等级最高的。如果还不唯一,选择最短的。
输入描述:
第1行:一个单独的整数N。第2到N + 1行:N个 [0, 221 - 1] 之间的整数,代表每头奶牛的被赋予的数。第j行描述了社会等级j - 1的奶牛。
输出描述:
第 1 行: 3个空格隔开的整数,分别为:最大的异或值,序列的起始位置、终止位置。
示例1
输入
[复制](javascript:void(0)😉
5
1
0
5
4
2
输出
[复制](javascript:void(0)😉
6 4 5
说明
最大异或值为6,从第4个开始喂,到第5个结束。
4 异或 2 = 6
(100) 异或 (010) = (110)
思路
对于问题我们可以转化成,求\(f[i] \oplus f[j - 1]\), 其中\(f\)为前缀异或数组,其异或表示了区间\([i, j]\)的异或值,题目要求我们维护右端点选择最小的,区间长度选择最小的。
这样问题就转变为了枚举\(f[i]\)通过Trie树选择一个\(f[j-1]\)和其异或最大, 其中\(j < i\)(我们可以一边插入一边枚举,保证了插入的前缀数\(f[j-1]\)都在其前面,另外要提前插入一个0,表示从1~i的前缀异或), 对于其左端点我们可以建立一个数组记录下标,每次更新下标,保证其在最右侧,这样维护了区间最小。
数组开到\(2^{21}\), 保证Trie树能够记录每一个bit位
复杂度: \(O(nlogn)\)
Code
#include <iostream>
#include <vector>
using namespace std;
const int N = 3e6;
int l, r, mx = -1, b[N];
struct trie{
int nex[N][2], idx;
bool exist[N];
void insert(int x) {
int p = 0;
for (int i = 20; i >= 0; i --) {
if (!nex[p][x>>i&1]) nex[p][x>>i&1] = ++idx;
p = nex[p][x>>i&1];
}
exist[p] = 1;
}
int find(int x) {
int p = 0, res = 0;
for (int i = 20; i >= 0; i --) {
if (!nex[p][!(x>>i&1)]) {
p = nex[p][x>>i&1];
if (x>>i&1) res += (1 << i);
}
else {
p = nex[p][!(x>>i&1)];
if (!(x>>i&1)) res += (1<<i);
}
}
return res;
}
};
int main() {
trie tree;
int n; cin >> n;
vector<int> a(n + 1), pre(n + 1);
tree.insert(0), b[0] = 1;
for (int i = 1; i <= n; i ++) {
cin >> a[i];
pre[i] = pre[i - 1] ^ a[i];
int res = tree.find(pre[i]);
if((pre[i] ^ res) > mx) {
l = b[res], r = i, mx = pre[i] ^ res;
}
b[pre[i]] = i + 1;
tree.insert(pre[i]);
}
cout << mx << " " << l << " " << r;
}