可持久化数据结构(一):可持久化Trie

近日学习了可持久化数据结构,因此特来总结一下,那就先从最简单的可持久化Trie开始吧!(虽然我理解这个的时间比主席树还久)

一:可持久化Trie的用途

正常的Trie树可以解决字符串的一些问题,特殊的Trie树(比如0/1Trie)可以解决最大异或和的相关问题,但是如果每次的询问是针对区间的,Trie树就不好解决,因为你不能对每个区间都建一棵Trie树,那样空间就会爆炸,于是,我们的可持久化Trie就登场了

二:可持久化Trie的构造

设trie[x][ch],表示从x号节点连向的字符为ch的点的编号(与普通Trie的含义相同),root[i]表示第i次插入的字符串的根节点,tot代表总节点数
可持久化Trie插入第i个字符串的构造流程如下:
1:首先新建第i个字符串的根节点,并定义两个变量p,q代表当前串的节点和上一个版本与之对应的节点,初始化p=root[i-1],q=root[i]
2:若当前字符串的下一个字符是ch,那么就让trie[q][ch]=++tot;
然后对于不是ch的字符CH,trie[q][CH]=trie[p][CH];
3:让p=trie[p][ch],q=trie[q][ch],并重复2,3,步直到建树完成

举个栗子

若待插入的字符串集为:cat,cup,soup,cut
第一次插入后的trie为(这些边都是有向边,从深度浅的指向深度深的,黑色节点代表根节点)
在这里插入图片描述
第二次为
在这里插入图片描述
第三次
在这里插入图片描述
第四次
在这里插入图片描述
这样,一颗可持久化Trie就建好了,我们从第i个根节点开始遍历,可以扫到第1~i次加入的字符串(可以自己看看)

可持久化Trie的遍历

可持久化数据结构的重要问题就是解决区间的查询问题,比如,如果要查询第L~R次插入的字符串,该怎样从树中遍历呢?
说实话,看了网上很多大佬的写法但都不是很懂,最终还是看了李煜东的《算法竞赛进阶指南》写法看懂了
首先解决右端点的问题,直接从第R个根节点开始遍历,可以确保不会遍历到R后面的字符串,至于左端点,我们引进一个数组Max[x],叶子结点的Max就是他属于的字符串是第几次插入的,其他节点的Max就是他所在子树中的Max最大值,在访问时,如果某个节点的Max<L,则说明这个节点在L的左端,直接返回就行了
红色节点代表Max值
在这里插入图片描述
例如我们要访问第2~3次插入的字符串,则应从第3个根节点进入,如果某个节点的值大于等于Max则可以访问,红色的边和点就是可以访问的
在这里插入图片描述

可持久化Trie的实现

void Insert(LL t,LL len,LL pre,LL now)
{
	if(len>=strlen(s[t]))
	{
		Max[now]=t;
		return;
	}
	LL ch=s[t][len]-'a'+1;
	if(pre) 
	{
		for(int i=1;i<=26;i++) 
		trie[now][i]=trie[pre][i];//复制上一个节点
	}
	trie[now][ch]=++tot;
	Insert(t,len+1,trie[pre][ch],trie[now][ch]);
	for(int i=1;i<=26;i++)
	Max[now]=max(Max[trie[now][i]],Max[now]);
}

其中t表示现在正在插入第t个字符串,len表示现在到了第len个字符,
查询类似的,就不写了

可持久化Trie的应用

[十二省联考2019]异或粽子

洛谷题目传送门
整体做法可以仿照 超级钢琴
首先区间异或值可以预处理出异或前缀和,因为异或有性质
a^a=0
所以区间L~R的异或值就为s[R]^s[L-1],s为异或前缀和
看到最大异或值,我们可以想到0/1Trie,
而看到区间,则可以想到可持久化Trie
所以我们就需要对异或前缀和建立可持久化0/1Trie,按照上述方法查询就行了

所以我们的整体思路就是,扫一遍数组,
假设这是右端点,然后再他左边找出和它异或最大的位置,然后把一个四元组(pos,l,r,ans)放进堆里,其中pos代表右端点位置,l代表左端点的最小范围,r代表左端点的最大范围,ans表示在(l~r)中和pos异或最大的位置,
然后每次从堆中取出异或值最大的区间,把这个贡献加上,然后把这个区间分为
(pos,l , ans-1)和(pos,ans+1,r)两个部分,在放入堆里,一共取出k次之后就行了

完整代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL N =  5e5+7;
LL n,k;
LL s[N],a[N],Max[N*64],trie[N*64][3],tot=0,rot[N];
struct node
{
	LL pos,l,r,ans;
};
node Make(LL a,LL b,LL c,LL d)
{
	node T;
	T.pos=a;
	T.l=b;
	T.r=c;
	T.ans=d;
	return T;
}
priority_queue<node> q;
bool operator < (const node &x,const node &y)
{
	LL a=s[x.ans]^s[x.pos];
	LL b=s[y.ans]^s[y.pos]; 
	return a<b;
}
void Insert(LL t,LL len,LL pre,LL pos)
{
	if(len<0)
	{
		Max[pos]=t;
		return;
	}
	LL ch=(s[t]>>len)&1;
	if(pre) trie[pos][ch^1]=trie[pre][ch^1];
	trie[pos][ch]=++tot;
	Insert(t,len-1,trie[pre][ch],trie[pos][ch]);
	Max[pos]=max(Max[trie[pos][ch]],Max[trie[pos][ch^1]]);
}
LL query(LL pos,LL val,LL len,LL L)
{

	if(len<0) return Max[pos]; 
	LL ch=(val>>len)&1;
	if(Max[trie[pos][ch^1]]>=L) return query(trie[pos][ch^1],val,len-1,L);
	else return query(trie[pos][ch],val,len-1,L); 
}
LL sum=0;
int main()
{
	Max[0]=-1;
	rot[0]=++tot;
	Insert(0,33,0,rot[0]);
	scanf("%lld%lld",&n,&k);
	for(LL i=1;i<=n;i++)
	scanf("%lld",&a[i]);
	for(LL i=1;i<=n;i++)
	{
		s[i]=s[i-1]^a[i];
		rot[i]=++tot;
		Insert(i,33,rot[i-1],rot[i]);
	}
	for(LL i=1;i<=n;i++)
	{
		LL ans=query(rot[i],s[i],33,0);
		q.push(Make(i,0,i,ans));
	}
	while(k--)
	{
		node tmp=q.top();
		q.pop();
		sum+=s[tmp.ans]^s[tmp.pos];
		if(tmp.l<tmp.ans) q.push(Make(tmp.pos,tmp.l,tmp.ans-1,query(rot[tmp.ans-1],s[tmp.pos],33,tmp.l)));
		if(tmp.r>tmp.ans) q.push(Make(tmp.pos,tmp.ans+1,tmp.r,query(rot[tmp.r],s[tmp.pos],33,tmp.ans+1)));
	}
	cout<<sum;
	return 0;
} 
  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值