【Trie树】| AcWing 143. 最大异或对

题目描述

在给定的N个整数A1,A2……AN选出两个进行xor(异或)运算,得到的结果最大是多少?

输入格式
第一行输入一个整数N。

第二行输入N个整数A1~AN

输出格式
输出一个整数表示答案。

数据范围

1≤N≤105,
0≤Ai<231

输入样例:

3
1 2 3

输出样例:

3

思路分析

0≤Ai<231,即最大值是231-1,即31个1,所以整数的二进制表示最大是31位,也就是说可以变成长度为31位的二进制字符串,所以进行异或运算的结果最大是1111……11 (31个1)

根据异或的性质,不同为1,相同为0,所以两位十进制数a和b,a^b,当挑选的a和b的每个二进制位都互不相同时,最终会得到最大的异或结果(31个1)
既然如此话,那么我们可以这么做,每一次检索的时候,我们都走与当前 ai 这一位相反的位置走,也就是让异或值最大,如果说没有路可以走的话,那么就走相同的路。

查找异或对的过程:

查找是相互的过程,a找到b等价于b找到a,所以与其找一个数可以找几个数匹配,倒不如让一个数可以被几个数匹配:
先把第一个数A1存入Trie树 (初始化) ,然后A2只能和Trie树中的A1匹配,匹配完后A2加入到Trie树,然后是A3,只能和Trie树中的A1或者A2匹配,找到结果最大的即可,然后是A4……依次类推,一直到AN

  1. ①与Ax和Trie树中A1到Ax-1之间的数匹配; ② Ax加入Trie树 。①②这两个的顺序可以没有先后要求,因为就算先把Ax插入到Trie树,自身与自身匹配结果为0,也没什么影响,不过还是建议先匹配再插入。
  2. 正常的顺序应该是Ax和Ax+1到AN之间的数,即匹配A1先和A2到AN之间的数匹配,A2和A3到AN之间的数匹配,但是这样不方便插入Trie树,所以采用逆向思维。

问题关键:

1. 二进制数存入Trie树

二进制数,只有0和1,所以分支只有两个。

每个十进制数唯一对应一个二进制数,因此Trie树中从第1层到第31层,只要一个分支不同,就是选择了截然不同的数。
为了得到的结果最大,应该先从最高位开始考虑,在最高位取得最优解的前提下,再去低位找。

e.g. 假如x=(10010)2 ,它的最大异或对应该是(01101)2,这样就是全1了。
如果trie树中有(01101)2的话,那么从高位开始匹配还是从低位开始匹配,匹配到的就是(01101)2
但是如果trie树中没有的话,假如Trie树中最大的是(01000)2 , 第0位和第2位取相同,其它位取不同,从最高位开始匹配可以找到(01000)2 ,但是如果从最低位匹配的话,最低位的0肯定要去找Trie树中的1,这样无论如何都匹配不到(01000)2 了。

2. 查找过程

给一个数x,查找Trie树中它的最大异或对:
从最高位开始检索,每一次检索的时候,我们都走与当前 ai 这一位相反的位置走,也就是让异或值最大,如果说没有路可以走的话,那么就走相同的路。

3. 把一个整数拆成31位二进制表示

int x,t;
for (int i=30;i>-1;i--)  t=x>>i&1; 

从最高位开始取,每次取一位。
且这里只能取1或0,不能取别的数,所以不能写成t=x&(1<

代码实现

分析231
210和103近似,所以230大概和109近似,即1e9,231大概是2e9,小于3e9,可以用int型定义trie二维数组。

#include 
#define read(x) scanf("%d",&x)

using namespace std;

const int N=1e5+10;
int a[N];
int trie[N*32][2],idx=1; "idx为0的时候存储根节点,已经默认了,下一个结点从1开始"

void insert(int x)
{
    int p=0; //从根节点0开始插入
    for (int i=30;i>-1;i--) { "要保证异或结果最大,从最高位开始取,共31位,所以共取30次"
        int t=x>>i&1;  "这是一个处理过程,01串中取出每一位,每种字符串不一样,小写字母串就处理成t=a[i]-'a',映射成列坐标"
        if (!trie[p][t]) trie[p][t]=idx++;
        p=trie[p][t];
    }
}

int find(int x) //找到一个和x异或结果最大的异或对
{
    int p=0,res=0;
    for (int i=30;i>-1;i--) {
        int t=x>>i&1;
        if (trie[p][!t]) res+=1<<i,p=trie[p][!t]; "优先考虑走不同分支,使该位的值为1,并加上相应的值:1<
        else p=trie[p][t];  "如果没有不同的分支,只能走相同的分支,那么该位就是0,不用加"
    }
    return res; //最后返回异或的结果
}

int main()
{
    int n;
    read(n);
    for (int i=0;i<n;i++) read(a[i]);
    insert(a[0]);
    int res=-1; "异或的结果必大于等于0"
    for (int i=1;i<n;i++) {
        res=max(res,find(a[i]));  //每次取最大的结果
        insert(a[i]);   "先比较,再插入Trie树"
    }
    printf("%d",res);
    
    return 0;
}

对find函数的不同思考,灵活使用位运算:

int find(int x)
{
    int p=0,res=0;
    for  (int i=30;i>-1;i--) {
        int t=x>>i&1;
        if (trie[p][!t]) res=res*2+1,p=trie[p][!t];
        else res=res*2,p=trie[p][t];
    }
    return res;
}

除了可以返回异或最大的结果,还可以返回和x异或的那个数:
所以,res=max(res,a[i]^find(a[i]));

int find(int x)
{
    int p=0,y=0;
    for  (int i=30;i>-1;i--) {
        int t=x>>i&1;
        if (trie[p][!t]) y=y*2+!t,p=trie[p][!t];
        else y=y*2+t,p=trie[p][t];
    }
    return y;
}
int find(int x)
{
    int p=0,y=0;
    for  (int i=30;i>-1;i--) {
        int t=x>>i&1;
        if (trie[p][!t]) y+=!t<<i,p=trie[p][!t];
        else y+=t<<i,p=trie[p][t];
    }
    return y;
}

你可能感兴趣的:(算法设计与分析入门,字符串,算法,数据结构)