最大异或和 (可持续化字典树)

传送门:最大异或和

前言:

这道题是一个经典的可持久化的题。可持久化的思想就是通过空间优化存储每一时刻的状态,以便在后续的询问中能够用快速查询。


思路:

此题问的是a[p] \oplus a[p+1] \oplus ... \oplus a[N] \oplus x的最大值。

那么先不考虑给定的范围\left [ l,r \right ]。将问题转化为找到一个位置p\epsilon \left [ 1,n \right ],使得a[p] \oplus a[p+1] \oplus ... \oplus a[N] \oplus x最大。

那么这个问题怎么解决呢?

这里需要补充一个前置知识:a[i] \oplus a[i+1] \oplus a[i+2] \oplus ... \oplus a[n]=(a[1]\oplus a[2] \oplus a[3] \oplus ... \oplus a[n]) \oplus (a[1] \oplus a[2] \oplus ... \oplus a[i-1])

那么我们可以先预处理一个sum[n]数组,用于存储前n项的异或和,再通过逐位去对比,找到一个满足条件的最大值即可。


 举个小小的栗子:

假设sum[n]的二进制表示为 1001010。

那么最理想的 能使sum[n] \oplus sum[i-1] 最大的sum[i-1]的二进制表示为0110101。

 但是不可能一定存在最理想的情况,所以就要每一位的去判断前面是否存在这种情况。


现在,思路已经出来了,建一颗01字典树就ok了,因为是详解,所以就画了一个字典树的建树图示。

定义:tre[n][2]为01字典树,里面存储的是达到当前状态的距离n最近的位置。

假设我们要建立的是:1001010,0010101,1001001

建树图示:(n=1,2,3的根节点实际上是同一个点,这里为了更好的表示,就画了三个不同的)


但是,擅长折磨我们的出题人可不会如此友善。

现在题目是给定一个[l,r],要求在这个范围内选择p。

显然,不可以只建一棵树,因为每加入一个点,就会更新当前点的信息,如果tre[now][1]>r就不能确定[l,r]   之间是否有满足条件的解了。

于是乎,可持久化闪亮登场了。

可持久化就是分别存储a[1]a[1]\oplus a[2]a[1]\oplus a[2] \oplus a[3]a[1]\oplus a[2] \oplus a[3] \oplus a[4],...,a[1]\oplus a[2] \oplus a[3] \oplus...\oplus a[n]

这样,给出了范围[l,r],就可以到第 r 棵树上进行匹配了,就不存在tre[now][1]>r的情况了。

可持续化的建树过程如图所示:

 


下面是AC代码。

这道题调的让我有跳楼的冲动,最后是逐行debug,哈哈哈。

#include<bits/stdc++.h>
using namespace std;
#define deb cout<<"find:::"<<endl
const int MAXN=6e5+5;
int sum[MAXN];	
int root[MAXN];
int tre[MAXN*25][2];
int flag[MAXN*25];
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	int n, m;
	cin >> n >> m;
	
	for (int i = 1; i <= n; i++) {
		int t;
		cin >> t;
		sum[i] = sum[i - 1] ^ t;
	}



	flag[0]=-1;
	int idx = 0;
	function<void(int, int, int, int)>build = [&](int id, int pos, int fa, int now) {
	//	cout<<"asda::"<<id<<' '<<pos<<' '<<fa<<' '<<now<<endl;
		if (pos < 0) {
			flag[now] = id;
			return;
		}
		int bit = sum[id] >> pos & 1;
		if(fa) tre[now][bit ^ 1] = tre[fa][bit ^ 1];
		tre[now][bit] = ++idx;
		build(id, pos - 1, tre[fa][bit], tre[now][bit]);
		flag[now] = flag[tre[now][bit]];
		
	};
	root[0]=++idx;
	build(0, 23, 0, root[0]);
	for (int i = 1; i <= n; i++) {
		root[i] = ++idx;
		build(i, 23, root[i - 1], root[i]);
	}
	
	function<int(int, int, int,int )>query = [&](int L, int now, int t,int k) {
		if (k < 0) {
			return t ^ sum[flag[now]];
		}
		int p = t >> k & 1;

		if (flag[tre[now][p ^ 1]] >= L) {
			return query(L, tre[now][p ^ 1], t, k - 1);
		}
		else return query(L, tre[now][p], t, k - 1);
	};
	for (int i = 1; i <= m; i++) {
	//	cout<<"asd"<<endl; 
		char op;
		cin >> op;
		if (op == 'A') {
			int x;
			cin >> x;
			n++;
			root[n] = ++idx;
			sum[n] = sum[n - 1] ^ x;
			build(n, 23, root[n - 1], root[n]);
		}
		else {
			int l, r, x;
			cin >> l >> r >> x;
			cout << query(l-1, root[r-1], sum[n] ^ x,23) << '\n';
		}
	}

	return 0;
}

关于代码:

可持久化是一种优化空间进行存储所有情况的一种数据结构。虽然已经极大程度的减少空间使用了,但其开辟的空间还是很大,所以,在写可持久化的题时,最好不要使用vector。一定要计算一下开的数组会不会爆MLE。

这题我因为没有初始化flag[0]=-1debug了三小时..........

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值