AcWing256 最大异或和 (可持久化01Trie)

4 篇文章 0 订阅

题目链接: 最大异或和

2021.7.30 重写了题解, 并且翻新了代码.

解题思路

思维

我们首先考虑查询操作:

假设原序列为a[], 我们可以拆开来看: 题目相当于要求 (a[p] ^ a[p + 1] ^ … ^ a[r]) ^ (a[r + 1] ^ a[r + 2] ^ … ^ a[n] ^ x)最大, 相当于我们把操作拆成了两部分, 我们发现后面这部分实际上是一个定值.

我们不难联想到最大异或对那道题. 区别就是, 前面这部分他是一个区间, 而不是一个整数. 如果是一个整数, 我们直接在trie树上查找即可.

考虑转化: 我们不妨处理出原序列的前缀和s[]. 这样题目的要求我们可以进而改写为: 找到一个p, 使得s[p - 1] ^ s[n] ^ x最大. 我们发现s[n] ^ x实际上是一定值, 此时如果trie树中维护s[], 这就变成了最大异或对题目的形式.

但唯一的区别就是, 我们并不能在整个trie树上进行查询, 我们要求这个s[p - 1]必须位于[l - 1, r - 1]区间.


可持久化01trie

想想我们怎么处理的区间第k大数? 把权值线段树可持久化即可. 对于本题也是这样.

我们用root[i]去记录[1, i]版本的trie树信息. 这样我们可以保证右端点在查询区间内.

对于左端点, 由于信息并不能进行线性的加减, 我们不能用主席树的思路来处理.
我们考虑对于trie树上的每一个节点记录一个时间戳, 来记录这个节点是在哪个位置被更新的. 这样我们每次用当前节点的时间戳与左端点进行比较即可.

AC代码

#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 6E5 + 10, M = N * 24, B = 24;
int root[N], t[M][2], ind;
int L[M]; // 时间戳
void insert(int a, int c, int p, int x) {
	for (int i = B - 1; i >= 0; --i) {
		int w = c >> i & 1;
		t[x][w] = ++ind, L[ind] = a;
		t[x][w ^ 1] = t[p][w ^ 1];
		p = t[p][w], x = t[x][w];
	}
}
int query(int c, int l, int x) {
	int res = 0;
	for (int i = B - 1; i >= 0; --i) {
		int w = c >> i & 1;
		if (L[t[x][w ^ 1]] >= l) {
			x = t[x][w ^ 1];
			res += 1 << i;
		}
		else x = t[x][w];
	}
	return res;
}

int main()
{
	int n, m; cin >> n >> m;

	L[0] = -1; // 空节点的时间戳设为一个较小值即可. 
    		   // (如果不设置, 在查询的时候需要进行判断是否存在这个节点.)
    
	root[0] = ++ind, insert(0, 0, 0, root[0]); //0号版本必须初始化.
    // 因为(p - 1) ∈ [l - 1, r - 1], 我们是可以取到s[0]的, 即: 我们选取了整个序列.
    // 如果我们不放这个0值, 当我们最终p取1时, 会出现未定义性错误.

	int sum = 0; //前缀和记录
	rep(i, n) {
		int x; scanf("%d", &x);
		sum ^= x;

		root[i] = ++ind;
		insert(i, sum, root[i - 1], root[i]);
	}


	rep(i, m) {
		char s[2]; scanf("%s", s);
		if (*s == 'A') {
			int c; scanf("%d", &c);

			sum ^= c;
			root[++n] = ++ind, insert(n, sum, root[n - 1], root[n]);
		}
		else {
			int l, r, c; scanf("%d %d %d", &l, &r, &c);
			printf("%d\n", query(sum ^ c, l - 1, root[r - 1]));
		}
	}

	return 0;
}

END

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逍遥Fau

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值