题目
思路
给n个数(1 2 3),怎么找最大异或?
- 什么是异或?
1. 先把数转换成二进制(每个数都有31位)
1: 000...0001
2: 000...0010
3: 000...0011
2. 按位异或(相同为0,不同为1)
1和2: 除了最后两位不一样,其他的都一样,所以结果为
000...0011(同样也是用二进制表示的数,转换成十进制就是3),即1和2异或的结果为3
同理,1^3=2,2^3=1
- 暴力解法就是每两个数都拿出来算一遍然后和目前最大的比较,得出最终结果,但是暴力会超时。优化解法是Trie树
- Trie树解法:
1. 还是先进行insert(),但是和之前字符串插入字符不同的是,这里是以0,1的形式插入。先把1,2,3这三个数都插到Trie树上。
Trie树:(省略一部分,只取最后四位)
root (空结点)
/
0
/
0
/ \
0 1
/ / \
1 0 1
1 2 3 这一行代表十进制数字
2. 然后看1(0001),第3位(位次顺序是3210)是u=0,i=3,就在root的子结点里找1(既然是最大异或,贪心思想,我们就尽量每一位都找不同的,这样就能保证找出来是最大的),发现没有1,那跟1成最大异或对的第三位只能是0了(p要指向子结点0)。继续看第2位是u=0,i=2,再在p指向的结点的儿子结点里找1,还是没有1,同理,p要指向子结点0。继续看第1位是u=0,i=1,同理可得,p要指向子结点1。继续看第0位u=1,同理可得,p要指向子结点0。
3. 重复步骤2,最终得到的最大异或对是3,也就是1^2。
代码
#include<iostream>
using namespace std;
const int N=100010,M=31*N;
int a[N];
int son[M][2],idx;//son[M][2]为什么是2列?因为每个结点的分支只有0,1,最多两个分支。字符串是26列是因为每个结点的分支最多有26个(26个英文字母)
int n;
void insert(int x)
{
int p=0;
for(int i=30;i>=0;i--)
{
int u=x>>i&1;
if(!son[p][u])
son[p][u]=++idx;
p=son[p][u];
}
}
int query(int x)
{
int p=0,ans=0;
for(int i=30;i>=0;i--)
{
int u=x>>i&1;//看第i位是0还是1
//关于">>":右移。
//eg:x=11101(二进制)
//x>>2,x右移两位,最后面两位就没了->(111)=3
//x>>i就相当于把第i位单独取出来,x>>i&1就是看这一位是0还是1,0&1=0,1&1=1
if(son[p][!u])//!u是因为要找异或,u=0,就看子结点有没有1;u=1,就看子结点有没有0
{
p=son[p][!u];//有符合要求的,p指向这个符合题意的结点,再找下一位的时候就从这个结点的子结点找
ans+=1<<i;//二进制数左移i位,高位舍弃,低位补0 (10<<2: 1010 -> 101000)
}
else//没有符合要求的(要求即u=0,找1,u=1,找0)
p=son[p][u];//u=0,没有1,符合题意的就是0;u=1,没有0,符合题意的就是1
}
return ans;
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)
cin>>a[i];//输入n个数
int ans=0;
for(int i=0;i<n;i++)
{
insert(a[i]);//插
ans=max(ans,query(a[i]));
}
cout<<ans<<endl;
return 0;
}
题目链接
https://www.acwing.com/problem/content/145/
如有不足或错误,请批评指正。