Loading

「刷题记录」BZOJ #4260. Codechef REBXOR

题面

\(\text{Description}\)
image
\(\text{Input}\)
输入数据的第一行包含一个整数N,表示数组中的元素个数。
第二行包含N个整数\(A1,A2,…,AN\)
\(\text{Output}\)
输出一行包含给定表达式可能的最大值。
\(\text{Sample Input}\)
\(5\)
\(1\ 2\ 3\ 1\ 2\)
\(\text{Sample Output}\)
\(6\)
\(\text{Hint}\)
满足条件的 \((l1,r1,l2,r2)\) 有:\((1,2,3,3)\)\((1,2,4,5)\)\((3,3,4,5)\)
对于 \(100\%\) 的数据,\(2 \le N \le 4*10^5\)\(0 \le Ai \le 10^9\)


这题数据太水
这就是一棵典型的 \(01\)\(\text{Trie}\),,先求出前缀异或和和后缀异或和,我们可以边插入边找最大异或和,建两棵 \(\text{Trie}\) 树,正着插入一遍,反着插入一边,最后枚举分界点即可
如何用字典树求两个数的最大异或?我们将两个数拆成二进制,用它们的二进制编码建树,想让他们的异或和最大,就是让每一位上都异或为 \(1\),相当于在 \(01\)\(\text{Trie}\) 中再设置一个指针,这个指针与我们插入新数字时向下走的方向相反,如果 \(cur\) 向右走,那它向左走,如果不能向左走(即左边没有节点),那就跟着向右走,又因为要保证最大,所以我们可以将高位建在靠上面的层中,将低位建在靠下的层中。
在本题中,求的是最大异或区间和,我们可以利用异或前缀和,求出异或前缀和,将每个点的值设置为异或前缀和的值,又因为求两端区间的最大加和,所以求出异或后缀和,再反着插入一遍,最后枚举分界点。
代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

inline ll read() {
	ll x = 0;
	int fg = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		fg |= (ch == '-');
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + (ch ^ 48);
		ch = getchar();
	}
	return fg ? ~x + 1 : x;
}

const int N = 4e5 + 5;

int n;
ll ans1[N], ans2[N], num[N], xo1[N], xo2[N];

struct Trie {
	int siz;
	int ch[10000005][2], val[10000005];
	
	Trie() {
		siz = 1;
		memset(ch[0], 0, sizeof ch[0]);
		val[0] = 0;
	}
	
	ll insert(ll x) {
		int cur = 0, pos = 0;
		for (int i = 31; i >= 0; -- i) {
			int c = (((1 << i) & x) == (1 << i));
			if (!ch[cur][c]) {
				memset(ch[siz], 0, sizeof ch[siz]);
				val[siz] = 0;
				ch[cur][c] = siz ++;
			}
			pos = ch[pos][c ^ 1] ? ch[pos][c ^ 1] : ch[pos][c];
			// 求最大异或和,就是能反着走就反着走,不能就正着走
			cur = ch[cur][c];
		}
		val[cur] = x;
		return val[pos] ^ val[cur];
	}
} trie1, trie2;

int main() {
	n = read();
	for (int i = 1; i <= n; ++ i) {
		num[i] = read();
		xo1[i] = xo1[i - 1] ^ num[i]; // 正向异或和
	}
	for (int i = n; i >= 1; -- i) {
		xo2[i] = xo2[i + 1] ^ num[i]; // 反向异或和
	}
	trie1.insert(0); // 提前插入一个0,让第一个进入的数有得异或
	trie2.insert(0);
	for (int i = 1; i <= n; ++ i) { // 正着插入
		ans1[i] = max(ans1[i - 1], trie1.insert(xo1[i]));
		// max 可以找到这个位置及之前的最大异或和
	}
	for (int i = n; i >= 1; -- i) { // 反着插入
		ans2[i] = max(ans2[i + 1], trie2.insert(xo2[i]));
		// min 可以找到这个位置及之后的最大异或和
	}
	ll maxn = 0;
	for (int i = 2; i <= n - 1; ++ i) { // 枚举分界点
		maxn = max(maxn, ans1[i] + ans2[i + 1]);
	}
	printf("%lld\n", maxn);
	return 0;
}
posted @ 2023-01-09 08:54  yi_fan0305  阅读(30)  评论(0编辑  收藏  举报