可持久化01字典树解析和模板

一、可持久数据结构

主要指的是我们可以查询历史版本的情况并支持插入,利用使用之前历史版本的数据结构来减少对空间的消耗(能够对历史进行修改的是函数式)。引用于https://www.cnblogs.com/BLADEVIL/p/3681291.html

其实就是我们的数据结构的内容会不断发生变化,而我们还要查询以前的历史版本,比如某个区间的情况。

PS:01字典树

二、例题

洛谷P4735
题目描述
给定一个非负整数序列 {a}{a},初始长度为nn。

有 mm 个操作,有以下两种操作类型:

A x:添加操作,表示在序列末尾添加一个数 xx,序列的长度 n+1n+1。
Q l r x:询问操作,你需要找到一个位置 pp,满足l≤p≤r,使得: a[p]⊕a[p+1]⊕…⊕a[N]⊕x 最大,输出最大是多少。
输入格式
第一行包含两个整数 N,M,含义如问题描述所示。
第二行包含 N个非负整数,表示初始的序列 A 。
接下来 M行,每行描述一个操作,格式如题面所述。

输出格式
假设询问操作有T 个,则输出应该有 T 行,每行一个整数表示询问的答案。
输入样例

5  5
2  6 4 3 6
A 1 
Q 3 5 4 
A 4
Q 5 7 0 
Q 3 6 6 

输出

4
5
6

说明/提示
N,M≤300000。0<=a[i]<=107
https://m-sea-blog.com/archives/1450/
截图于洛谷榜一题解https://m-sea-blog.com/archives/1450/

三、思路:

我们要维护的是的异或和s[]并在任意区间内找到s[p]使之与(s[n]^x)取得最大异或值,——异或+区间

求异或最值我们需要建立01字典树,但是这题是有区间限制的,那么我们必须能保存任意区间[l.r]的情况。
n个数我们建立n个字典树,第i颗字典树保存1~i的数,和每个节点经过的次数sum[],那么我们要求区间[l,r]的那些数出现过时,我们可以拿第r棵01字典树减去第l-1棵01字典树得到(其实**就是sum值相减,相减后某个节点的sum值大于0就说明在区间内出现过**)

我们真的要建立n棵01字典树吗?那么空空间可想而知会非常大!建01字典树是要的,但是我们想想要如何减少空间消耗

四、实现

我们需要依次n个数的01字典树,并且能查询1-i(i<=n)的情况,相减得到任意区间的情况。我们只需建一个01字典树(n个01字典子树合成的)即可,它包含n个数,每插入一个数(第i个数),都新建一个节点,为了保存历史版本当然要和前i-1个子树连上关系(要复制以前的版本),由于这些子树是一颗树,我们只需要知道根节点即可得到整棵树的情况,所以如果第i个数位上如果某位上不错在某个值,这个值的就存第i-1字典树的值,即把以前版本都保留下来了,还节省了空间这就是可持久化

这个过程要有一个数组root[]保存n个历史字典树的根,查询[l,r]区间的数的情况时只需看sum[tree[r][id]]和sum[tree[l-1][id]]哪个大即可,如果前者大,说明在[l,r]区间某些数某位存在值为id的情况,然后继续贪心选位就行了
还要一个sum[]数组,记录每个节点经过的次数。

五、c++模板
(其实就比普通01字典树多了几行代码,里面有详细注释)

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define max_size 600005
int tree[32*max_size][2];
int val[32*max_size];
int sum[32*max_size];//每个节点经过的次数 
int root[max_size];//每个历史版本的树根 
int sumxor[max_size];//前缀异或和 
int a[max_size];//输入序列 
int num=0;
void insert(int xi,int x)//第xi棵 
{
	root[xi]=++num;
	int per=root[xi-1];//上一棵树的根 
	int now=root[xi];//当前树的根 
	for(int i=31;i>=0;i--){
		int id=(x>>i)&1;
		tree[now][id]=++num;//因为是另一棵新树,所以直接新建,不用判断是否存在
		tree[now][id^1]=tree[per][id^1];//当前位用不上这个值,就直接继承以前的子树 
		now=tree[now][id];
		per=tree[per][id];
		sum[now]=sum[per]+1; 
	}
	val[now]=x;
	return;
}
int query(int l,int r,int x)//在[l+1,r]找一个与x异或最大的数 
{
	l=root[l];r=root[r];
	for(int i=31;i>=0;i--){
		int id=(x>>i)&1;
		if(sum[tree[r][id^1]]>sum[tree[l][id^1]])//在[l,r]内此位为id^1的数存在 
		{
			l=tree[l][id^1];r=tree[r][id^1];
		}
		else//不存在,当前位只能选存在的id 
		{
			l=tree[l][id];r=tree[r][id]; 
		}
	}
	return val[r];
}
int main(void)
{
	int n,m,x,l,r;
	char c;
	scanf("%d %d",&n,&m); 
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		sumxor[i]=sumxor[i-1]^a[i];
		insert(i,sumxor[i]);
	}
	while(m--)
	{
		getchar();
		scanf("%c",&c);
		if(c=='A')//添加
		{
			n++;
			scanf("%d",&x);
			sumxor[n]=sumxor[n-1]^x;
			insert(n,sumxor[n]);
		}
		else
		{
			scanf("%d %d %d",&l,&r,&x);
			l--;r--;//别忘了 
			//找sumxor[p] p∈[l-1,r-1]使得sumxor[p]^sumxor[n]^x最大 
			printf("%d\n",sumxor[n]^x^query(l-1,r,sumxor[n]^x));
		}
	}
	return 0;
}

六、注意:

l--;r--;

这一行是因为在这里插入图片描述
另外小心RE

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值