acwing——143. 最大异或对

92 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第34天,点击查看活动详情

题目描述

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

输入格式

第一行输入一个整数 N。

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

输出格式

输出一个整数表示答案。

数据范围

1N1051≤N≤10^5, 0Ai<2310≤Ai<2^31

输入样例:

3
1 2 3

输出样例:

3

想想最朴素的做法:暴力循环

最简单的做法就是每一个数和其他的数进行一一比较,然后会有一个异或对的结果

想想看,我们要怎么完成这个循环?难道是最麻烦的从左到右吗?不是,对比的原则是

  • 每插入一个数,我们将这个数和之前的全部数字进行比较即可,不需要真的将所有的数和其他数字比较一般,这样太麻烦了,也太耗时了。
  • 分析一下时间复杂度吧,按照这样的做法的话,时间复杂度的计算是一个等差数列的求和计算方法,好的看看n是多少——10510^5,我的评价是6,时间复杂度差不多是5×1095×10^9,C++是快,但是这样子做绝对会爆时间的,所以我们需要有其他的算法来完成

解决方法:Tire树存储

我们将一个数抽象成为一个Tire树,比如样例:1 2 3
1的二进制数是1
2的二进制数是10
3的二进制数是11
那么他们的Tire树大致的结构就是:

image.png 所以这就是一个简单的Tire树结构,那么怎么算最大的异或对呢?

计算异或对

首先看看样例:1 2 3
1和2比->01和10,结果就是11->3
1和3比->01和11,结果就是10->2
2和3比->10和11,结果技术01->1
我们会发现,二进制从前往后比较如果越前的位数越不同(01)的话,异或对就越大,所以我们参照之前等差数列的那种异或求法,每个数插入之后,先存储进Tire数,然后比较二进制从前往后的数值,尽量往数字越不同的地方前进,这样的好处就是只需要在若大的数字里面只要在Tire数里面走一次
我将会在下面的代码加上足够的代码解释:

Tire树代码如下:

#include<iostream>
#include<algorithm>
using namespace std;

////M代表一个数字串二进制可以到多长
const int N = 100010, M = 3100010;

int n;
int a[N], son[M][2], idx;

void insert(int x) {
	//根节点
	int p = 0;
	for (int i = 30; i >= 0; i--) {
		int u = x >> i & 1;//先左移i位,就可以取出第i位的数字,然后再异或一下是1还是0
		//如果没找到这条路,开辟一条新路
		if (!son[p][u])son[p][u] = ++idx;
		//指向下一层
		p = son[p][u];
	}
}

int search(int x) {
	int p = 0, res = 0;
	//从最大位开始比较
	for (int i = 30; i >= 0; i--) {
		int u = x >> i & 1;
		//根据异或的最大值,我们要找不同的,如果有不同的就走另一条条路,如果没有那只好走u这条路
		if (son[p][!u]) {
			p = son[p][!u];
			res = res * 2 + 1;//相当于左移一位
		}
		else {
			p = son[p][u];
			res = res * 2;
		}
	}
	return res;
}

int main()
{
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
	cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> a[i];
		insert(a[i]);
	}

	int res = 0;
	for (int i = 0; i < n; i++)res = max(res, search(a[i]));

	cout << res;
	return 0;
}

解题结束~