异或
是一种逻辑运算,用于比较两个位(bit),当且仅当两个比较的位不同(一个为0,另一个为1)时,结果为1(真)。如果两个比较的位相同(都是0或都是1),结果为0(假)。在数学符号中,异或通常用符号“⊕”表示。
异或运算的规则如下:
• 0 ⊕ 0 = 0
• 0 ⊕ 1 = 1
• 1 ⊕ 0 = 1
• 1 ⊕ 1 = 0
直接暴力
显然会超时,这是需要考虑把这一步优化:
优化图解:
结论:
字典树不单单可以高效存储和查找字符串集合,还可以存储二进制数字
思路:
将每个数以二进制方式存入字典树,找的时候从最高位去找有无该位的异.
❶建树时,根据输入数字的对应的二进制串构造一个 Trie 树。
❷Trie 树的每个结点两个分支,分支指向的两个son结点分别表示当前位的数值为0或1
❸记录每次输入的数字转化成的二进制串,当前位为1,就走到数值为1的结点,否则走到0结点
❹这样每个数字对应的Trie中的路径就是唯一的。
因为要求异或对的值最大,可以用贪心的思想。
在第一个数字固定的情况下,尽可能地让第二个数的每一位都与第一个数的对应位相反,这样最终确定的第二个数与第一个数的异或值就最大,所以在查询时,遍历第一个串o(n),根据固定的第一个二进制串,每次尽可能走到与当前位的值相反的结点,这样的路径对应的就是与第一个二进制串异或值最大的二进制串,便利了这个数的位数次o(logn),所以总的时间复杂度o(n*logn);。
AC代码
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
//保存 Trie 树
int son[N * 31][2];
int n, idx;
void insert(int x)
{
int p = 0;//初始化指向根节点
//从最高位开始,依次取出每一位
for (int i = 31; i >= 0; i--)
{ // 取出当前位
int u = x >> i & 1;
//如果树中不能走到当前数字
//为当前数字创建新的节点,保存该数字
if (!son[p][u])
// 新节点编号为 idx + 1
son[p][u] = ++idx;
p = son[p][u];
}
}
int query(int x)
{
//指向根节点
int p = 0;
// 保存与 x 异或结果最大的那个数
int ret = 0;
//从最高位开始,依次取出 x 的每一位
for (int i = 31; i >= 0; i--)
{
// 取出 x 的当前位
int u = x >> i & 1;
//如果树中能走到 !u,就走到!u
if (son[p][!u]){
//走到!u
p = son[p][!u];
//更新 x 异或的对象
ret = ret * 2 + !u;
}
//没有!u,就只能走到u了
else{
p = son[p][u];
//更新 x 异或的对象
ret = ret * 2 + u;
}
}
//计算异或结果
ret = ret ^ x;
return ret;
}
int main()
{
cin >> n;
int maxXorNum= 0;
int x;
for (int i = 0; i < n; i++)
{
cin >> x;
insert(x);
maxXorNum = max(maxXorNum, query(x));
}
cout << maxXorNum << endl;
return 0;
}