统计异或值在范围内的数对有多少(01字典树)

1803. 统计异或值在范围内的数对有多少 - 力扣(Leetcode)


题意:

 

 

 


思路:

  • 很经典的方法,01字典树,同时在树上多维护一个数据,有多少个节点经过当前节点,记为cnt
  • 我们可以写出一个query(x,tar)函数,x时num数组中的数,答案就是query(x,high)- query(x,low-1)
  • 构造一棵空树,然后在每个节点插入,按照 x 向下查找位置时,记录经过的点在之前有多少个节点经过(因为题目有一个i < j 的条件),然后讨论从高位往低位
    1. tar 当前第 i 位二进制为 1 时,说明异或对结果的第 i 位可以为 0 或 1。往异或结果为 0 的子节点走,之后所有的必然都比 tar 小,可以直接加上这个子节点的cnt;往异或结果为 1 的子节点走,之后需再判断
    2. tar 当前第 i 位二进制为 0 时,说明异或对结果的第 i 位只能为 0。只能往对应的子节点走
    3. 在上面的走法,如果无法走,就停止,返回已经算出来的数量
  • 像这样计算一个,插入一个就能得到答案了

const int N = 20005;
int trie[N * 15][2], cnt[N * 15], idx;
class Solution { 
public:
    int countPairs(vector<int>& nums, int low, int high) {
        return get(nums, high) - get(nums, low - 1);
    }
    int get(vector<int>& nums, int high) {
        idx = 0;
        memset(trie, 0, sizeof(trie));
        memset(cnt, 0, sizeof(cnt));
        int ans = 0;
        for (int i = 0; i < nums.size(); i++) {
            ans += query(nums[i], high);
            add(nums[i]); 
        }
        return ans;
    }
    void add(int x) {
        int p = 0;
        for (int i = 14; i >= 0; i--) {
            int u = (x >> i)  & 1;
            if (trie[p][u] == 0) trie[p][u] = ++idx;
            p = trie[p][u]; //移动到下一个结点 
            cnt[p]++; // 个数增加,cnt[x]代表x结点出现的次数
        }
    }
    int query(int x, int high) {
        int sum = 0, p = 0;
        for  (int i = 14; i >= 0; i--) {
            int u = (x >> i) & 1;
            if (((high >> i) & 1) == 1) { //high当前i位为1, 那么x与以前数当前i位的异或可以位1或者0
                sum += cnt[trie[p][u]];//加上与x异或后当前i位为0的数量
                if (trie[p][u ^ 1] == 0) return sum; //没有结点可以继续走下去,直接返回
                p = trie[p][u ^ 1]; //继续往异或的结点走下去
            } else { //high当前i位为0, x与以前数异或的第i为必须为0
                if (trie[p][u] == 0) return sum; //没有结点走下去
                p = trie[p][u]; //寻找与x的第i位相同的进制,异或结果为0
            }
        }
        sum += cnt[p]; //加上走到最后的结点数
        return sum;
    }
};

 

posted @ 2023-01-07 13:35  俄罗斯刺沙蓬  阅读(39)  评论(0编辑  收藏  举报
返回顶端