You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

133 lines
4.5 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

##[$AcWing$ $143$. 最大异或对](https://www.acwing.com/problem/content/145/)
### 一、题目描述
在给定的 $N$ 个整数 $A_1A_2……A_N$ 中选出两个进行 $xor$(异或)运算,得到的结果最大是多少?
**输入格式**
第一行输入一个整数 $N$。
第二行输入 $N$ 个整数 $A_1A_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$。