题目

传送门

解法

\(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;
}
posted on 2020-02-23 11:07  Oxide  阅读(149)  评论(0编辑  收藏  举报