这道题其实可以用前缀和。异或运算有\(pre[r]\) \(xor\) \(pre[l-1]==a[l]\) \(xor\) \(a[l+1]\) \(xor...xor\) \(a[r]\)。
我们把前缀和二进制分解插入字典树上,模拟异或运算。将新的前缀和与之前的前缀和匹配。
解释一下代码吧。
设当前询问的前缀和为\(x\)。首先我们必须保证\(i-1\)位异或后大于等于\(k\)的\(i-1\)位。就先这样假设。
- 若\(k\)第\(i\)位是\(0\):我们知道如果这一位我们选择\(x[i]\) \(xor\) \(1\)的方向,这一位异或的答案会是\(1\),那么在这之后的所有情况一定都是合法的,我们直接累加,跳过即可。显然我们选择\(x[i]\)这个方向。
- 若\(k\)第\(i\)位是\(1\):我们只能使异或答案为\(1\)。选择\(x[i]\) \(xor\) \(1\)的方向。
这样下去我们最后走出来的东西一定与\(k\)相等。(当然可能这样的串不存在)加上\(siz\)即可。
还有一点,因为是前缀和,我们需要提前插入\(0\)。
#include<cstdio>
const int N = (1 << 24) + 2;
int n, k, sum, ch[N][2], siz[N], cnt = 1;
long long ans;
int read() {
int x = 0, f = 1; char s;
while((s = getchar()) > '9' || s < '0') {if(s == '-') f = -1;}
while(s <= '9' && s >= '0') {
x = (x << 1) + (x << 3) + (s ^ 48);
s = getchar();
}
return x * f;
}
void insert(const int x) {
int p = 1;
for(int i = 30; i >= 0; -- i) {
int op = (x >> i) & 1;
if(! ch[p][op]) ch[p][op] = ++ cnt;
++ siz[p]; p = ch[p][op];
}
++ siz[p];
}
void ask(const int x) {
int p = 1;
for(int i = 30; i >= 0; -- i) {
int op = (x >> i) & 1;
if(! ((k >> i) & 1)) ans += siz[ch[p][op ^ 1]], p = ch[p][op];
else p = ch[p][op ^ 1];
}
ans += siz[p];
}
int main() {
n = read(), k = read();
insert(0);
for(int i = 1; i <= n; ++ i) {
sum ^= read();
ask(sum); insert(sum);
}
printf("%lld\n", ans);
return 0;
}