可持久化trie树

基本思想 :

可以发现,如果裸着建,不仅要消耗很多的时间,更是要消耗很多的空间。考虑以i为根的字典树和以(i-1)为跟的字典树的异同。
可以发现,在当前以i为根的字典树上减去a[i],就是(i-1)的字典树了。所以,我们可以将除了a[i]之外的结点都连到(i-1)的树上。
当然,i-1的树也是从i-2的树上引用过来的。
这样之后,会出现一个问题。比如,a[i-1]的值与a[i]的值相同,那么我们到底要不要跟新呢?还是直接将root[i]全部指向root[i-1]?
还是之前的做法。除了a[i]的值,其他的值全部引用前面的,将a[i]跟新root[i]。
这样做完之后,可以发现,如果不加以区分,root[i]和root[i-1]的树一模一样,直接做差之后不就没有了。可是事实上,在【i,i】这给区间内,有一个数的值为a[i]。
所以我们用一个sum数组加以区别,也就是说,如果当前的root的结点是引用前面的,那么sum不变。否则(就是a[i]跟新的部分),sum[root[i]]=sum[[root[i-1]]+1。a[i]这个数的每个位置都是之前的+1。。

void insert(int x ,   int last,int &now)
{
	now = ++ cnt ;
	int p = now ;
	for(int i = 30 ;i >= 0 ;i --)
	 {
	 	int id = (x >> i) & 1 ;
	 	num[p] = num[last] + 1 ;
		son[p][id] = ++ cnt ;
		son[p][id ^ 1] = son[last][id ^ 1] ;
		p = son[p][id] , last = son[last][id] ; 
	 }
	 num[p] = num[last] +  1; 
}

这样我们在判断区间的时候,就可以拿sum[root[r]]-sum[root[l-1]]。如果大于0,说明在root[l-1]之后,被跟新过,所以可以取。如果sum值相同,代表root[r]中的那部分是直接引用的root[l-1]中的那部分,这个区间内没有跟新过,所以无法取
查询的时候贪心的走就行了,

int ask(int x , int l , int r )
{
	int ans = 0 ;
	for(int i = 30 ;i >= 0 ;i --)
	 {
	 	int id = (x >> i) & 1 ;
	 	if(num[son[r][id ^ 1] ]- num[son[l][id ^ 1]]> 0)
	 	 ans += 1 << i , l = son[l][id ^ 1] , r = son[r][id ^ 1] ;
	 	else l = son[l][id] , r = son[r][id] ;
	 }
	 return ans ;
}

最大异或和

#include <bits/stdc++.h>
using namespace std ;
#pragma GCC optimize(3 , "Ofast" , "inline")
const int N = 3e5 + 10 ;
int num[N << 6] , son[N << 6][2] ;
int n , m , a[N << 6] , rt[N << 6];
int cnt ;
void insert(int x ,   int last,int &now)
{
	now = ++ cnt ;
	int p = now ;
	for(int i = 30 ;i >= 0 ;i --)
	 {
	 	int id = (x >> i) & 1 ;
	 	num[p] = num[last] + 1 ;
		son[p][id] = ++ cnt ;
		son[p][id ^ 1] = son[last][id ^ 1] ;
		p = son[p][id] , last = son[last][id] ; 
	 }
	 num[p] = num[last] +  1; 
}
int ask(int x , int l , int r )
{
	int ans = 0 ;
	for(int i = 30 ;i >= 0 ;i --)
	 {
	 	int id = (x >> i) & 1 ;
	 	if(num[son[r][id ^ 1] ]- num[son[l][id ^ 1]]> 0)
	 	 ans += 1 << i , l = son[l][id ^ 1] , r = son[r][id ^ 1] ;
	 	else l = son[l][id] , r = son[r][id] ;
	 }
	 return ans ;
}
int main()
{
	int n , m , x , l , r  ;
	scanf("%d%d" ,&n , &m) ;
	for(int i = 1; i <= n ;i ++)
	 {
	 	scanf("%d" , &x) ;
	 	a[i] = a[i - 1] ^ x ;
	 	insert(a[i] , rt[i - 1] , rt[i]) ;
	 }	
	 char str[5] ;
	 for(int i = 1 ;i <= m ;i ++) 
	  {
	  
	  	scanf("%s" , str) ;
	  	if(str[0] == 'A')
	  	 {
	  	 	scanf("%d" , &x) ;
	  	 	a[n + 1] = a[n] ^ x ;
			n ++ ; 
		    insert(a[n] , rt[n - 1] , rt[n]) ;
		 }
		else 
		 {
		 	scanf("%d%d%d"  , &l , &r , &x) ;
		 	l -- , r -- ;
		 	if(l == 0) 
		 	 printf("%d\n"  , max(ask(x ^ a[n] , 0, rt[r]) , x ^ a[n])) ;
		 	else 
		 	 printf("%d\n" , ask(x ^ a[n] , rt[l - 1] , rt[r])) ;
		 }
		 
	  }
	return 0 ;
}

全部评论

相关推荐

01-23 19:11
已编辑
门头沟学院 前端工程师
点赞 评论 收藏
分享
02-07 12:06
已编辑
华侨大学 测试开发
最近看到很多&nbsp;92&nbsp;的,甚至是硕士,开始往测开赛道卷,说实话有点看不懂。先把话说清楚,大厂里的测开,绝大多数时间干的还是测试的活,只是写点自动化脚本、维护测试平台、接接流水线,真正像开发一样做系统、做架构、做核心平台的测开少得可怜,基本都集中在核心提效组,而且人很少,外面进去的大概率轮不到你,我想真正干过人都清楚。很多人被洗脑了,以为测开也是开,和后端差不多,只是更简单、更轻松、还高薪。现实情况是,测开和开发的职业路径完全不一样。开发的核心是业务和系统能力,测开的核心是稳定性和覆盖率,前者是往上走,后者天花板非常明显。你可以见到很多开发转测开,但你很少见到干了几年测开还能顺利转回开发的。更现实一点说,92&nbsp;的高学历如果拿来做测开,大部分时间就是在做重复性很强的杂活,这种工作对个人能力的放大效应非常弱。三年下来,你和一个双非的,甚至本科的测开差距不会太大,但你和同龄的后端、平台开发差距会非常明显。这不是努不努力的问题,是赛道问题。所谓测开简单高薪,本质上是把极少数核心测开的上限,当成了整个岗位的常态来宣传。那些工资高、技术强的测开,本身就是开发水平,只是挂了个测开的名。普通人进去,99%&nbsp;做的都是项目兜底型工作,而不是你想象中的平台开发。测开不是不能做,但它绝对不是开发的平替,也不是性价比最优解。如果你是真的不想做开发,追求稳定,那测开没问题。但如果你只是觉得测开比后端容易,还能进大厂,那我劝你冷静一点,这只是在用短期安全感换长期天花板。有92的学历,如果你连测开这些重复性工作都能心甘情愿接受,那你把时间精力用在真正的开发、系统、业务深度上,回报大概率比卷测开要高得多。想清楚再下场,别被岗位名和话术带偏了,就算去个前端客户端也是随便占坑的,测开是一个坑位很少赛道,反而大面积学历下放,不用想也能知道会是什么结果,我想各位在JAVA那里已经看到了
小浪_Coding:工作只是谋生的手段 而不是相互比较和歧视
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务