最大异或和
前缀和适用范围 XOR性质
首先说明以下XOR是异或的意思,写作
⊕
\oplus
⊕
这里我们定义广义前缀和:
在a
[
0
]
∼
a
[
n
]
序列上定义运算
⋅
则前缀和表示为a
[
0
]
⋅
a
[
1
]
⋅
a
[
3
]
⋯
a
[
n
]
\text{在a}\left[ 0 \right] \sim \mathrm{a}\left[ \mathrm{n} \right] \text{序列上定义运算}\cdot \,\,\text{则前缀和表示为a}\left[ 0 \right] \cdot \mathrm{a}\left[ 1 \right] \cdot \mathrm{a}\left[ 3 \right] \cdots \mathrm{a}\left[ \mathrm{n} \right]
在a[0]∼a[n]序列上定义运算⋅则前缀和表示为a[0]⋅a[1]⋅a[3]⋯a[n]
只要
⋅
能够进行逆运算就可以使用前缀和的性质
\text{只要}\cdot \text{能够进行逆运算就可以使用前缀和的性质}
只要⋅能够进行逆运算就可以使用前缀和的性质
对于此处的
⊕
,
可以证明
(
k
⊕
n
)
⊕
n
=
k
\text{对于此处的}\oplus ,\text{可以证明}\left( \mathrm{k}\oplus \mathrm{n} \right) \oplus \mathrm{n} =\,\,\mathrm{k}
对于此处的⊕,可以证明(k⊕n)⊕n=k
对于S
[
i
]
则有S
[
j
∼
i
]
=
S
[
i
]
⊕
S
[
j
−
1
]
\text{对于S}\left[ \mathrm{i} \right] \text{则有S}\left[ \mathrm{j}\sim \mathrm{i} \right] \,\,=\,\,\mathrm{S}\left[ \mathrm{i} \right] \oplus \mathrm{S}\left[ \mathrm{j}-1 \right]
对于S[i]则有S[j∼i]=S[i]⊕S[j−1]
相当于S[i]可以把S[j-1]的部分剔除,则可以使用前缀和.
前4n-1个数异或和为0,具体有 3 7 11 15 19 23 27 31 35 39 43 47 …,原理是每个数加到4,末位会出现00,01,10,11,对应位上两个1异或为0
解法1 暴力 O ( n 2 ) O(n^2) O(n2)
我们可以枚举i,j作为起点和终点,预处理前缀和后每次计算m长度内的最大异或值,存max输出.
#include <iostream>
using namespace std;
const int N = 1e6 + 10, M = 31 * N;
int a[N];
int s[N];
int get(int l, int r) {
//由于n^a^a等价于n自身,故前缀和计算时可以通过异或l-1部分来把r中l-1的部分去掉
return s[r] ^ s[l - 1];
}
int main() {
int n, m;
cin >> n >> m;
int x, maxn = 0;
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
s[i] = s[i - 1] ^ a[i];//前缀异或和
}
int res = 0;
for (int i = 1; i <= n; ++i) {
for (int j = i; j <= n && j < i + m; ++j) {
res = max(res, get(i, j));//
}
}
cout << res << endl;
return 0;
}
这样可以过80%数据
解法2 Tire树+滑动窗口 O ( n l o g n ) O(nlogn) O(nlogn)
与最大异或对类似,可以构建数字trie树,trie树存的是当前所有异或对.通过query查询最大值
由于本题目限制子数组长度m,故能够比较的字串限制在m以内
详细代码
注意看推的样例!
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
const int N = 100010 * 31, M = 100010;
int n, m;
int s[M];
int son[N][2], cnt[N], idx;
void insert(int x, int v) {//插入/删除x 取决于v是1还是-1
int p = 0;
for (int i = 30; i >= 0; i--) {
int u = x >> i & 1;
if (!son[p][u]) son[p][u] = ++idx;
p = son[p][u];
cnt[p] += v;
}
}
int query(int x) {//查询最大值
int res = 0, p = 0;
for (int i = 30; i >= 0; i--) {
int u = x >> i & 1;
if (cnt[son[p][!u]])
p = son[p][!u], res = res * 2 + 1;
else
p = son[p][u], res = res * 2;
}
return res;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
int x;
scanf("%d", &x);
s[i] = s[i - 1] ^ x;
}
int res = 0;
insert(s[0], 1);//至少要插入0,与0异或是自己
for (int i = 1; i <= n; i++) {
if (i > m) insert(s[i - m - 1], -1);//剔除前面的
res = max(res, query(s[i]));//意会一下
//要求的是长度<=m的最大异或值,,每次i更新,都相当于走到当前m区间的最后一个数字,前面的数字都已经插入trie集合,
//且前面的最大值已经更新,然后当前的数字只需要进入trie集合找最大值即可 这个是从1开始递推的 比如m=3
//i为1 trie集合只有0,最大就是1处的值 0101
//i为2 trie集合0101,当前1000,异或得1100,1100为最大值,同时1000存入trie
//i为3 trie 0101,1000 ,当前1110,按照trie树走第一位取0,故按照0101异或,得1011 最大值不更新
//i为4 trie 1000,1110(0101已经被删除),当前0001,按照trie树走1110,得1111,更新为最大值
/*
上述例子输入测试
4 3
5
8
14
1
结果为15 也就是1111
*/
insert(s[i], 1);//插入当前序列
}
printf("%d\n", res);
return 0;
}