给定一个非负整数数列
a
a
a,初始长度为
N
N
N。
请在所有长度不超过
M
M
M的连续子数组中,找出子数组异或和的最大值。
子数组的异或和即为子数组中所有元素按位异或得到的结果。
注意:子数组可以为空。
输入格式
第一行包含两个整数
N
,
M
。
N,M。
N,M。
第二行包含
W
W
W个整数,其中第i个为
a
i
a_i
ai。
输出格式
输出可以得到的子数组异或和的最大值。
数据范围
对于20%的数据,1≤M≤W≤100
对于50%的数据,1≤M≤W≤1000
对于100%的数据,1≤M≤N≤105,0≤a,≤231-1
输入样例:
3 2
1 2 4
输出样例:
6
分析:
要求连续的子数组中的最大异或和,首先想想怎么遍历连续的子数组,这个时候考虑用前缀异或和
比如sum[i] =
a
1
a_1
a1 ^
a
2
a_2
a2 ^
a
3
a_3
a3……^
a
i
a_i
ai,要求区间[l,r]的子数组那就等于sum[r] ^ sum[l-1];
因为sum[r] =
a
1
a_1
a1 ^
a
2
a_2
a2 ^
a
3
a_3
a3……^
a
r
a_r
ar,sum[l-1] =
a
1
a_1
a1 ^
a
2
a_2
a2 ^
a
3
a_3
a3……^
a
l
−
1
a_{l-1}
al−1,由于异或的性质a^ a = 0,0 ^ a = a,故前面l-1个数全部变为0,从l开始到r开始异或。接着利用贪心的思想,枚举前缀异或和,我们希望与这个前缀异或和的每一位都不同,那么异或之后那一位就是1,这样能使异或之后尽可能大。那么怎么存储这些位数呢,用trie树,字典树被用来高效地存储和查询异或前缀和。每一层存储对应位数是1还是0;再用一个cn数组来维护数组最大长度为M。
下面是完整代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 1e5 + 10; // 数列长度上限
const int M = 31 * N; // 字典树节点最多有N*31个
int p[M][2], ct[M], idx; // 字典树、计数数组和节点索引
int sum[N]; // 存储前缀异或和
// 初始化字典树和全局变量
void init() {
memset(p, 0, sizeof(p)); // 初始化字典树的所有指针为0
memset(ct, 0, sizeof(ct)); // 初始化计数数组为0
idx = 1; // 节点索引从1开始,因为0已经被用作根节点
}
// 插入或删除一个前缀和到字典树中
void insertt(int u, int c) {
int t = 0; // 字典树的根节点起始索引
for (int i = 30; i >= 0; i--) { // 对于每一位
int x = u >> i & 1; // 取出当前位的值(0或1)
if (!p[t][x]) { // 如果路径不存在,则创建一个新节点
p[t][x] = ++idx;
}
t = p[t][x]; // 移动到下一个节点
ct[t] += c; // 更新计数,c为1表示插入,为-1表示删除
}
}
// 查询与给定前缀和异或值最大的数
int query(int u) {
int t = 0;
int res = 0;
for (int i = 30; i >= 0; i--) { // 从最高位到最低位
int x = u >> i & 1; // 获取当前位
int want = x ? 0 : 1; // 获取我们想要的相反的位
// 如果相反的位存在,并且当前滑动窗口内有这样的数
if (ct[p[t][want]] > 0) {
res |= (want << i); // 设置结果的当前位为1
t = p[t][want]; // 沿着这个分支向下
} else {
res |= (x << i); // 设置结果的当前位保持不变
t = p[t][x]; // 沿着这个分支向下
}
}
return res ^ u; // 返回最大异或和
//这个res就是某个前缀,异或之后必能取到某个连续的数组,比如最大异或和数组是下标为4,5这两个异或
//那么当i等于5的时候,查找长度为M的子数组时,可以找到最优的前缀异或和sum[3],在和sum[5]异或之后
//就得到了结果,更具体一点:res 是根据与 u (当前的前缀和)异或后能得到最大值的前缀和构建的。
//res 的每一位都是通过字典树搜索得到的,并且尽可能与 u 中的对应位相反(通过选择相反位的路径),从而使得 u XOR res 的结果尽可能大。
}
int main() {
init(); // 初始化字典树和相关变量
int n, m;
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%d", &sum[i]);
sum[i] ^= sum[i - 1]; // 计算前缀异或和
}
insertt(0, 1); // 插入0,处理空子数组的情况
int res = 0; // 存储最大异或和
for (int i = 1; i <= n; i++) {
if (i > m) {
insertt(sum[i - m - 1], -1); // 当前滑动窗口的前一个数,从字典树中删除
}
res = max(res, query(sum[i])); // 在滑动窗口内查询当前前缀和的最大异或结果
insertt(sum[i], 1); // 插入当前前缀和到字典树中
}
printf("%d\n", res); // 输出最大异或和
return 0;
}