字典树-异或问题

题目描述

给定整数m以及n各数字A1,A2,…An,将数列A中所有元素两两异或,共能得到n(n-1)/2个结果,请求出这些结果中大于m的有多少个。

输入描述:
第一行包含两个整数n,m.
第二行给出n个整数A1,A2,…,An。

对于30%的数据,1 <= n, m <= 1000
对于100%的数据,1 <= n, m, Ai <= 10^5
示例1
输入
3 10
6 5 10
输出
2

求解思路

容易知道如果两个数字异或的结果res大于m, 那么一定是从某一位开始 res处为1 m处为0
那么对于一个数a,其寻找满足异或结果大于m的另一个数字b的过程可以如下:
从index位=》0位,即高位到低位

如果a=0 m=0 =》那么可以此时b=1满足要求,以及b=0继续判断下一位
如果a=1 m=0 =》那么可以此时b=0满足要求,以及b=1继续判断下一位
如果a=0 m=1 =》那么可以此时只有b=1时,并且需要继续判断下一位
如果a=1 m=1 =》那么可以此时只有b=0时,并且需要继续判断下一位

具体做法:
将arr[]中所有的数字组织成为一个01字典树
然后眼睛arr[]中每一个数字arr[i]可以与字典树中那些数字异或=》累加到sum
最后sum/=2 (因为一对符合要求的组合计算了2次)
实现细节:
题目给定的数字范围其不超过2的16次方,因此可直接研究数的低16位

代码实现

#include<iostream>
#include<vector>
using namespace std;
struct trie_node   //字典树节点
{
    int count;  //以当前节点为前缀的数量
    trie_node* next[2];
    trie_node()
    {
        count=0;
        next[0]=NULL;
        next[1]=NULL;
    }  
};

trie_node* create_trie(vector<int> arr,int index)  //根据arr构建字典树  从index位直到第0位
{
    trie_node* root=new trie_node();
    
    for(int i=0;i<arr.size();i++) //每个数存入trie中
    {
        int num=arr[i];
        trie_node* p=root;//每个数从根 开始插入
        for(int j=index;j>=0;j--)
        {
            if((num>>j)&1) //该位为1 需要衍生节点
            {
                if(p->next[1]==NULL)
                    p->next[1]=new trie_node();
                p=p->next[1];
                p->count+=1; //对应位置节点数目++ 
            }
            else
            {
                if(p->next[0]==NULL)
                    p->next[0]=new trie_node();
                p=p->next[0];
                p->count+=1; //对应位置节点数目++ 
            }
        }
    }
    return root;
}
//该函数用于查找在root字典树中与 a异或大于m的数有多少 从index位=》0 开始分析
long long query(trie_node* root,int a,int m,int index)
{
    if(root==NULL)
        return 0;
     int i1=((a>>index)&1);//a的index位置
     int i2=((m>>index)&1); //m的index位置
    if(i1==0 && i2==0) //两个数这个位置都是0 
    {
        long long sum1=root->next[1]==NULL? 0:root->next[1]->count;
        long long sum2=query(root->next[0],a, m,index-1);//在子树中寻找
        return sum1+sum2;
    }
    else if(i1==0 && i2!=0) //a为0 m不为0  那么必须其他一个数为1
    {
        if(root->next[1]==NULL)
            return 0;
        return query(root->next[1],a,m,index-1);//在子树中寻找
        
    }
    else  if(i1!=0 && i2==0) //a不为0 m为0
    {
        long long sum1=root->next[0]==NULL? 0:root->next[0]->count;//先累加上所有另外数字为0的
        long long sum2=query(root->next[1],a, m,index-1);//在另外数字为1的上寻找
        return sum1+sum2;
    }
    else //两个数这个位置都是1
    {
        if(root->next[0]==NULL)
            return 0;
        return query(root->next[0],a,m,index-1);//只能在另一个数字为0中寻找
    }
      return 0;//其他情况返回0 
}

int main()
{
    int n,m;
    cin>>n>>m;
    vector<int> arr(n);
    for(int i=0;i<n;i++)
    {
        cin>>arr[i];
    }
    trie_node* root=create_trie(arr,16);//
    long long count=0;
    for(int i=0;i<n;i++)
    {
        count+=query(root,arr[i],m, 16);
    }
    cout<<count/2<<endl;//注意最终的结果除以2

}
已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 撸撸猫 设计师:C马雯娟 返回首页