AcWing 143. 最大异或对(Trie树)

题目链接 :点击查看

题目描述 :

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

输入输出格式 :

输入

第一行输入一个整数 N。

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

输出

输出一个整数表示答案。

输入输出样例 :

输入

3
1 2 3

输出

3

题目分析 :

若用暴力做法,本题应该这样做,通过两重循环对区间内的数两两进行异或操作,并通过res记录其中的最大值,时间复杂度为o(n ^ 2), 在1e5的数据范围下时间肯定超限,而且本题的最大时间复杂度为O(n log n)。所以对于本题,我们可以用Trie树进行优化,可以这样想,把数据(十进制)转化为二进制,每个Trie树节点储存这个数据不同位的值,因此每个节点只有0和1两种状态,在创建son数组时,应为son[M][2](特别地这里的 M 设定为 3e6 + 1e5 + 7,因为元素 < 2 ^ 31, 所以每个数最多有31位 所以应大于 1e5 * 31),同时定义cur表示要建立的节点编号(当前到哪一个节点)。然后我们将数据构建Trie树,在insert()函数中,将每个数从高位到低位依次建立节点,如节点已存在则不用创建,具体做法为用一个for循环,i 从 30 开始 (因为每个数最多为31位,没到31位的高位默认为0),计算当前位(i)是0还是1,即 x >> i & 1 的值,计算出的0或1作为当前节点的子节点,如其子节点不存在即!son[p][ x >> i & 1 ] 则 令son[p][ x >> i & 1 ] = ++ cur表示建立新节点,之后当前节点指针p指向下一个节点(即子节点) p = son[p][ x >> i & 1 ]。上述是数据二进制值Trie数的构建过程,接着我们要根据异或操作的性质,算出每个数据异或后(跟谁异或?)最大值。关于异或操作,如果两个数当前位的数不同(一个是0,一个是1)那么异或后是1,反之则为0,如果两个数每一位都不同,则异或后的值就是最大值。这里我们采取贪心策略 :从高位开始比较两个数是否每一位数是否相同,如相同,则从当前节点继续移动下一个节点比较下一位,如不同, 则用res记录当前生成的1所在位置(或直接生成值),从已经找到的那个节点移动到子节点继续向下比较。具体做法为,我们创建一个search()函数, 同样从i = 30开始,循环计算传入数的每一位,如果Trie树种存在与当前位不同的数(一个是0,一个是1)即if(son[p][!s]) 我们用 res 记录异或后生成的值即 res += 1 << i ,然后以找到的此节点为基础继续向下移动p = son[p][!s],如不存在,则从当前节点移动到下一位p = son[p][s]。详见如下代码。

代码 :

#include
#include
using namespace std;
const int N = 1e5 + 7, M = 3e6 + 1e5 + 7;
int arr[N], son[M][2], cur;
void insert(int x) {
	int p = 0;
	for (int i = 30; i >= 0; i -- ) {
	 if (!son[p][x >> i & 1]) son[p][x >> i & 1] = ++ cur;
	 p = son[p][x >> i & 1];
	} 
}
int search(int x) {
	int p = 0, res = 0;
	for (int i = 30; i >= 0; i -- ) {
		int s = x >> i & 1;
		if (son[p][!s]) {
			res += 1 << i;
			p = son[p][!s];
		}
		else {
			p = son[p][s];
		}
	} 
	return res;
	
}
int main() {
	int n;
	cin >> n;
	for (int i = 0; i < n; i ++ ) {
	    cin >> arr[i];
	    insert(arr[i]);
	}
	int res = 0;
	for (int i = 0; i < n; i ++ )  res = max(res, search(arr[i]));
	cout << res ;
	return 0;
}

-------------------------------------------------------

你可能感兴趣的:(早年算法竞赛学过的知识点,Trie)