|
|
##[$AcWing$ $143$. 最大异或对](https://www.acwing.com/problem/content/145/)
|
|
|
|
|
|
### 一、题目描述
|
|
|
在给定的 $N$ 个整数 $A_1,A_2……A_N$ 中选出两个进行 $xor$(异或)运算,得到的结果最大是多少?
|
|
|
|
|
|
**输入格式**
|
|
|
第一行输入一个整数 $N$。
|
|
|
|
|
|
第二行输入 $N$ 个整数 $A_1~A_N$。
|
|
|
|
|
|
**输出格式**
|
|
|
输出一个整数表示答案。
|
|
|
|
|
|
**数据范围**
|
|
|
$1≤N≤10^5,0≤Ai<2^{31}$
|
|
|
|
|
|
**输入样例:**
|
|
|
```cpp {.line-numbers}
|
|
|
3
|
|
|
1 2 3
|
|
|
```
|
|
|
**输出样例:**
|
|
|
```cpp {.line-numbers}
|
|
|
3
|
|
|
```
|
|
|
|
|
|
### 二、分析思路
|
|
|
先来思考暴力怎么做:
|
|
|
``` c++
|
|
|
// 最大异或对,用暴力是超时的
|
|
|
// 通过了 6/10个数据
|
|
|
#include<iostream>
|
|
|
using namespace std;
|
|
|
const int N=1e5+10;
|
|
|
int a[N];
|
|
|
int res;
|
|
|
int main(){
|
|
|
int n;
|
|
|
cin>>n;
|
|
|
for(int i=1;i<=n;i++) cin>>a[i];
|
|
|
|
|
|
for(int i=1;i<=n;i++)
|
|
|
for(int j=i+1;j<=n;j++)
|
|
|
res=max(res,a[i]^a[j]);
|
|
|
cout<<res<<endl;
|
|
|
return 0;
|
|
|
}
|
|
|
```
|
|
|
结果不出意外,$TLE$,只能想办法进行优化
|
|
|
|
|
|
#### $Trie$树思路
|
|
|
|
|
|
1、将整数解析为二进制数,即有符号整数,$31$位,就是$0-30$,按$Trie$树进行存储, **整数的$Trie$树存储**。
|
|
|
|
|
|
2、每个数字的每一个二进制位,需要从高位到低位,即`for(int i = 30; i >= 0; i--)`,想像一下你在构建一个$Trie$树,那么根$root$就是最高位,然后一路走到$31$位,就是最低位。
|
|
|
|
|
|
3、每个数字想要找到与自己形成最大异或值的另一个数字,我们现在已经把它们保存到$Trie$树里了,那怎么找呢?什么样的两个数字才是最大异或值的对呢?就是每一位完全相反的就肯定是最大的异或对!那如果某一位相反的结点并不存在呢?这就是退而求其次的思路了,我们尽量从左到右找出与当前数字本位相反的路径,如果存在,就继续探索,如果不存在,那就使用一样的本位值。这样下来,到$31$位,就可以找到和自己匹配最大的异或值。
|
|
|
|
|
|
#### 总结一下
|
|
|
- $Trie$里可以用来保存数字,数字需要通过二进制(由高位到低位)进行保存。
|
|
|
|
|
|
- 增加一个数字进来,其实就是增加了一个层级为$31$级的 **模拟字符串**
|
|
|
|
|
|
- 放入一个数字,那么它肯定会在任意一级(共$31$级)存在一边,另一边可能存在,也可能不存在。
|
|
|
|
|
|
|
|
|
### 三、实现代码
|
|
|
```cpp {.line-numbers}
|
|
|
#include <bits/stdc++.h>
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
|
const int N = 1e5 + 10;
|
|
|
const int M = N * 31;
|
|
|
|
|
|
int n, res;
|
|
|
int a[N];
|
|
|
int tr[M][2];
|
|
|
int idx;
|
|
|
|
|
|
// 构建数字二进制位的Trie树
|
|
|
void insert(int x) {
|
|
|
int p = 0;
|
|
|
for (int i = 30; i >= 0; i--) {
|
|
|
int u = (x >> i) & 1; // 取出当前位的值
|
|
|
if (!tr[p][u]) tr[p][u] = ++idx; // 构建Trie树
|
|
|
p = tr[p][u];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 所谓与x异或最大,就是利求在高位上尽量不一样,如果找不到不一样的,就只能找一样的,下一个继续优先找不一样的
|
|
|
// 在Trie树中查找到与x异或最大的数
|
|
|
int query(int x) {
|
|
|
int p = 0, ans = 0;
|
|
|
for (int i = 30; i >= 0; i--) {
|
|
|
int u = (x >> i) & 1; // 取出x的当前二进制位
|
|
|
if (tr[p][!u]) { // 如果存在可以异或的路可以走的话,尽量先走
|
|
|
p = tr[p][!u];
|
|
|
ans = ans * 2 + !u; // 还原二进制数字为十进制
|
|
|
} else {
|
|
|
p = tr[p][u]; // 否则只能走与自己本位一样的路线
|
|
|
ans = ans * 2 + u; // 还原二进制数字为十进制
|
|
|
}
|
|
|
}
|
|
|
return ans;
|
|
|
}
|
|
|
|
|
|
int main() {
|
|
|
cin >> n;
|
|
|
for (int i = 1; i <= n; i++) cin >> a[i], insert(a[i]);
|
|
|
|
|
|
for (int i = 1; i <= n; i++) {
|
|
|
int t = query(a[i]);
|
|
|
res = max(res, a[i] ^ t);
|
|
|
}
|
|
|
printf("%d", res);
|
|
|
return 0;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### 五、对于一维数据范围的思考
|
|
|
无论是模板题还是最大异或对着一题
|
|
|
都有这么一行代码
|
|
|
`if (!tr[p][u]) tr[p][u] = ++ idx; p = tr[p][u];`
|
|
|
|
|
|
所以我们可以知道,$tr$数组的一维下标最大值的选取实际上是跟$idx$能够自增多少次来决定的
|
|
|
|
|
|
**[$AcWing$ $835$. $Trie$字符串统计](https://www.acwing.com/problem/content/837/)** 中,输入的字符串总长度不超过 $10^5$,所以一维值选取$1e5+10$
|
|
|
|
|
|
|
|
|
而在 **[$AcWing$ $143$. 最大异或对](https://www.acwing.com/problem/content/description/145/)** 中,数字需要以$2$进制进行表示,而每个数字最大为$2$的$31$次幂,所以一维下标应为数字的个数*$31$。
|
|
|
|