SPOJ SUBXOR
题意
给定一个由正整数构成的数组, 求 异或和小于k 的子序列的个数.
题解
假设答案区间为 [L, R], XOR[L, R] 等价于 XOR[1, L - 1] ^ XOR[1, R], 可以使用 01Trie 保存目前已有的 前缀异或和, 对于每一个新的前缀插入之前, 在 01Trie 中查询 与 新的前缀 异或值 小于 K 的 已有前缀和的个数.
对于每个TrieNode 的定义为
struct TrieNode {
TrieNode* next[2];
int cnt;
TrieNode() {
next[0] = next[1] = NULL;
// 保存当前前缀的个数
cnt = 0;
}
};
在进行查询时, 比较 新的前缀和 and k 的每一位
已有前缀和的第 i 位 | indexPre( 新的前缀和的第 i 位) | indexK( K 的第 i 位) | 相应操作 |
---|---|---|---|
0 | 0 | 0 | 递归求解左子树 |
1 | 0 | 1 | 统计左子树叶子节点个数, 递归求解右子树 |
1 | 1 | 0 | 递归求解右子树 |
0 | 1 | 1 | 统计右子树叶子节点个数, 递归求解左子树 |
对于 indexPre == 0, indexK == 0 的情况来说, 已有前缀和为 0 时满足条件, 因此需要递归求解左子树. 当已有前缀和为 1 时, indexK == 1, 大于要求的值, 所以不继续递归.
对于 indexPre == 0, indexK == 1 的情况来说, 已有前缀和为 1 时满足条件, 但 右子树 中可能有 值大于等于 K 的叶子节点, 因此需要递归求解右子树. 当已有前缀和为 0 时, indexK == 0, 所有左子树的叶子节点的值均小于 K, 因此统计左子树叶子节点的个数
AC代码
#include <cstdio>
#include <iostream>
using namespace std;
struct TrieNode {
TrieNode* next[2];
int cnt;
TrieNode() {
next[0] = next[1] = NULL;
cnt = 0;
}
};
void insertNum(TrieNode* root, unsigned num) {
TrieNode* p = root;
for(int i = 31; i >= 0; i--) {
int index = (num >> i) & 1;
if(!p->next[index])
p->next[index] = new TrieNode();
p = p->next[index];
p->cnt++;
}
}
int getCnt(TrieNode* root) {
return root ? root->cnt : 0;
}
int queryLessThanK(TrieNode* root, int pre, int k) {
TrieNode* p = root;
int ret = 0;
for(int i = 31; i >= 0; i--) {
if(p == NULL)
break;
int indexPre = (pre >> i) & 1; // prefiexbit
int indexK = (k >> i) & 1; // bit
if(indexPre == indexK) {
if(indexK)
ret += getCnt(p->next[1]);
p = p->next[0];
}
else if(indexPre != indexK) {
if(indexK)
ret += getCnt(p->next[0]);
p = p->next[1];
}
}
return ret;
}
int main() {
int nTest; scanf("%d", &nTest);
while(nTest--) {
int nNum, k;
scanf("%d %u", &nNum, &k);
TrieNode* root = new TrieNode();
// insertNum(root, 0) 保证了前缀异或和 pre 自身 可以小于 k
insertNum(root, 0);
unsigned pre = 0;
long long ans = 0;
while(nNum--) {
unsigned num; scanf("%u", &num);
pre = pre ^ num;
ans += queryLessThanK(root, pre, k);
insertNum(root, pre);
}
cout << ans << endl;
}
return 0;
}