题意:
给定一个整数数组$nums$和$low$与$high$,
问对于$nums$中的数对,$low \leq nums[i] \ XOR\ \ nums[j] \leq high$的数对个数。
数据范围:$1 \leq low, high, nums[i], nums.size() \leq 2 \times 10^4$
题解:
考虑第$i$个数与当前已经插入的前$i-1$个数中异或值小于$limit$的数的个数。
-
当$bit[i][k] = 0 且 bit[limit][k] = 0$:
此时只能匹配$trie$树中第$k$位为$0$的,并到下一层进行判断。
因为匹配$trie$树中第$k$位为$1$的会使得最终异或值大于$limit$ -
当$bit[i][k] = 0 且 bit[limit][k] = 1$:
此时若匹配$trie$树中第$k$位为$0$的,则最后的异或值一定小于$limit$,直接加上该点表示的所有数加上即可。
此时若匹配$trie$树中第$k$位为$1$的,那需要到下一层进行判断。 -
当$bit[i][k] = 1 且 bit[limit][k] = 0$:
此时只能匹配$trie$树中第$k$位为$1$的,并到下一层进行判断。
因为匹配$trie$树中第$k$位为$0$的会使得最终异或值大于$limit$ -
当$bit[i][k] = 1 且 bit[limit][k] = 1$:
此时若匹配$trie$树中第$k$位为$1$的,则最后的异或值一定小于$limit$,直接加上该点表示的所有数加上即可。
此时若匹配$trie$树中第$k$位为$0$的,那需要到下一层进行判断。
代码:
class Solution {
public:
static const int N = 20010 * 20;
int tr[N][2], cnt[N], idx = 0;
void insert(int x) {
int p = 0;
for(int i = 16; i >= 0; --i) {
int u = x >> i & 1;
if(!tr[p][u]) tr[p][u] = ++idx;
p = tr[p][u];
++cnt[p];
}
}
int query(int x, int limit) {
int res = 0, p = 0;
for(int i = 16; i >= 0; --i) {
int u = x >> i & 1, v = limit >> i & 1;
if(u == 0 && v == 0) p = tr[p][0];
else if(u == 0 && v == 1) res += cnt[tr[p][0]], p = tr[p][1];
else if(u == 1 && v == 0) p = tr[p][1];
else if(u == 1 && v == 1) res += cnt[tr[p][1]], p = tr[p][0];
if(!p) break;
}
return res;
}
int countPairs(vector<int>& nums, int low, int high) {
int res = 0;
for(auto &u : nums) {
res += query(u, high + 1) - query(u, low);
insert(u);
}
return res;
}
};
query(u, high + 1) - query(u, low);为什么查询的limit不是小于等于呢
因为求等于的情况需要跑到树的最深处,这样就可能出现往两边跑的情况