题目
解法
将 \(a\) 序列做一个异或前缀和,我们就将问题转化为查询一个数与 \(\mathtt{trie}\) 树内数字异或值 \(\ge k\) 的个数。
比如设当前询问的前缀和为 \(x\)。
- 若 \(k\) 第 \(i\) 位是 \(0\):如果这一位选择 \(x[i]\oplus 1\) 的方向,这一位异或的答案会是 \(1\),那么在这之后的所有情况一定都是合法的,我们直接累加,跳过即可。然后选择 \(x[i]\) 这个方向。
- 若 \(k\) 第 \(i\) 位是 \(1\):我们只能使异或答案为 \(1\)。选择 \(x[i]\oplus 1\) 的方向。
注意需要提前插入 \(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;
}