trie树(三) 可持久化

边刷边更新...

可持久化操作相信大家都不陌生,那么trie树的可持久化其实跟主席树差不多。

那么跟主席树差不多,我们在建树的时候肯定是选择在每次插入的时候值更新改更新的节点,其他的就继承之前的版本就可以了。

 这里展示一下01trie的可持久化的建树操作,那么其实普通字符串的也是同理。

解释都放在了代码里。

void insert(int pre,int now,int i,int k)
{//之前版本的根位置,当前版本的根位置,插入的序号,数字遍历到了哪一位(这里是从后往前插入)
	if(k<0)
	{
		last[now]=i;//更新改字符串的最晚插入的版本
		return;
	}
	int id=(mas[i].val>>k)&1;//要插入的是什么字符
	if(pre) tr[now][id^1]=tr[pre][id^1];//继承之前的版本
	tr[now][id]=++cnt;//新建一个节点
	insert(tr[pre][id],tr[now][id],i,k-1);//递归插入字符串
	last[now]=max(last[tr[now][0]],last[tr[now][1]]);//更新改字符串的最晚插入的版本
}

接下来考虑怎么遍历。如果是01trie的话,我们还要考虑怎么求异或最大值。想想看普通的trie是怎么求异或最大值的,就是从后往前按位贪心嘛,那么这个也是同理,只不过可持久化的trie可以应用于区间罢了,原理是一样的。但是怎么做到只在要求的区间里面遍历呢?仔细观察一下我上面写的初始化代码,有去维护一个last数组,它的意思是每一个节点最晚在哪一个版本被更新,那么加入我们要查询的版本在【l,r】之间的话,首先从r版本的根位置开始向下查询,肯定是不会超过r的,所以我们只需要关注左边界。那么只要对应的节点的last值>=l,我们不就可以访问了吗,就是这样。

ll query(int l,int r,int val)//查询 
{//l是你的左边界,r是右边界对应的根的位置,val就是你要查询的值
	int p=r;
	for(int i=31;i>=0;--i)
	{
		int id=(val>>i)&1;
		if(last[tr[p][id^1]]>=l) p=tr[p][id^1];//只有这一句跟普通的trie有一点不一样。
		else p=tr[p][id];
	}
	return ...
}

最后还有一点细节,直接跟着题目走一遍吧。 

最大异或和

大意:

思路:

就是不断更新数组,以及对应的异或前缀和。

code:

#include<bits/stdc++.h>
using namespace std;
#define ll int
#define endl '\n'
const ll N=6e5+10;
ll idx=0;
ll tr[N*25][3];
ll root[N];
ll last[N*25];
ll sum[N];
ll n,m;
char op;
ll l,r,val;
ll a,b,c;
void insert(ll pre,ll now,ll i,ll k)
{
	if(k<0)
	{
		last[now]=i;
		return;
	}
	bool id=(sum[i]>>k)&1;
	if(pre) tr[now][id^1]=tr[pre][id^1];
	tr[now][id]=++idx;
	insert(tr[pre][id],tr[now][id],i,k-1);
	last[now]=max(last[tr[now][0]],last[tr[now][1]]);
}
ll find(ll l,ll now,ll val)
{
    ll p=now;
    for(int i=23;i>=0;--i)
    {
    	bool id=(val>>i)&1;
    	if(last[tr[p][id^1]]>=l) p=tr[p][id^1];
    	else p=tr[p][id];
	}
	return sum[last[p]]^val;
} 
void solve()
{
	cin>>n>>m;
	root[0]=++idx;
	last[0]=-1;
	insert(0,root[0],0,23);
	for(int i=1;i<=n;++i)
	{
		cin>>a;
		sum[i]=sum[i-1]^a;
		root[i]=++idx;
		insert(root[i-1],root[i],i,23);
	}
	while(m--)
	{
		cin>>op;
		if(op=='A')
		{
			cin>>a;
			n++;//n++操作要在语句外执行,不能写成sum[++n]=sum[n-1]^a,会wa,我也不懂。。。 
			sum[n]=sum[n-1]^a;
			root[n]=++idx;
			insert(root[n-1],root[n],n,23);
			continue;
		}
		cin>>a>>b>>c;
		cout<<find(a-1,root[b-1],sum[n]^c)<<endl;
	}
}
int main()
{ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	solve();
	return 0;
}

通过这题,我们就学会了如何求在序列 a 的一个给定区间 [l, r]中选择一个数 ai​ 与另一个给定的值异或起来最大。nice~

[HEOI2013]ALO

大意:

有序列 a ,选择一个连续区间,使得最大化区间次大值和区间其他数异或的最大值,保证序列中的数两两不相等。

思路:
n的范围不大,所以我们很自然地就会想到以每一个数字作为区间次大值去找到它对应的最大的答案,那么前提就是我们能为每一个元素找到一个或若干个连续区间满足它是区间的次大值,然后就可以套可持久化01trie了。但是仔细想一下,很显然,满足该性质的区间只有两种:

我们设要查询的数字在第i位。

1.在i左边找到第二个比他大的数字在j位置,那么一个满足条件的区间就是(j,i];

1.在i右边找到第二个比他大的数字在j位置,那么一个满足条件的区间就是[i,j);

所以我们的最终目标是要在这两个区间里找到对应的与ai异或之后取到最大值的两个数字再取优,那这不是可以合并成一个区间取最优的吗?所以我们其实只用对每一个元素找一个区间就好了。另外,这个区间在满足条件的情况下肯定是要越大越好,这样才越有可能去更新最大值。

这里注意一下,整个序列的最大值无论如何都不会是某一个区间的次大值,所以我们最后查询的时候跳过他(虽然好像不跳过也没有影响)。

最后考虑一下如何更新每一个元素的合法区间:

可以使用链表,维护每一个元素的合法区间的左端点和右端点(开区间,因为好转移),然后按权值从小到大来更新区间,因为较大元素的合法区间肯定覆盖它相邻较小元素的合法区间。然后每一个元素的最后的合法区间就是pre[id]=l[l[id]];nxt[id]=r[r[id]];

for(int i=1;i<=n;++i)
	{
		int id=mas[i].id;
		r[l[id]]=r[id];l[r[id]]=l[id];//为下一个区间更新用
		pre[id]=l[l[id]];nxt[id]=r[r[id]];
		//cout<<l[id]<<' '<<pre[id]<<endl;
	}

code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define IL inline
#define endl '\n'
const ll N=1e5+10;
int n;
int l[N],r[N];
int pre[N],nxt[N];
namespace FastIOT{
	const int bsz=1<<18;
	char bf[bsz],*hed,*tail;
	inline char gc(){if(hed==tail)tail=(hed=bf)+fread(bf,1,bsz,stdin);if(hed==tail)return 0;return *hed++;}
	template<typename T>IL void read(T &x){T f=1;x=0;char c=gc();for(;c>'9'||c<'0';c=gc())if(c=='-')f=-1;
	for(;c<='9'&&c>='0';c=gc())x=(x<<3)+(x<<1)+(c^48);x*=f;}
	template<typename T>IL void print(T x){if(x<0)putchar(45),x=-x;if(x>9)print(x/10);putchar(x%10+48);}
	template<typename T>IL void println(T x){print(x);putchar('\n');}
}
using namespace FastIOT;
struct ty
{
	int val;
	int id;
}mas[N];
int al[N]; 
bool cmp(ty a,ty b)
{
	return a.val<b.val;
}
int tr[N*34][3];
int ma=0;
int root[N];//每一个数字的根节点 
int cnt=0;
int last[N*34];//最后添加的版本 
void insert(int pre,int now,int i,int k)
{
	if(k<0)
	{
		last[now]=i;
		return;
	}
	int id=(mas[i].val>>k)&1;
	if(pre) tr[now][id^1]=tr[pre][id^1];
	tr[now][id]=++cnt;
	insert(tr[pre][id],tr[now][id],i,k-1);
	last[now]=max(last[tr[now][0]],last[tr[now][1]]);
}
ll query(int l,int r,int val)//查询 
{
	int p=r;
	for(int i=31;i>=0;--i)
	{
		int id=(val>>i)&1;
		if(last[tr[p][id^1]]>=l) p=tr[p][id^1];
		else p=tr[p][id];
	}
	return val^al[last[p]];//返回最大异或结果 
}
void solve()
{
	read(n);
	for(int i=1;i<=n;++i)
	{
		l[i]=i-1;r[i]=i+1;
		read(mas[i].val);
		al[i]=mas[i].val;
		ma=max(ma,mas[i].val);
		mas[i].id=i;
		root[i]=++cnt;//新建一个节点 
		insert(root[i-1],root[i],i,31);//插入trie树 
	}
	l[1]=0;r[n]=n;
	sort(mas+1,mas+1+n,cmp);
	for(int i=1;i<=n;++i)
	{
		int id=mas[i].id;
		r[l[id]]=r[id];l[r[id]]=l[id];
		pre[id]=l[l[id]];nxt[id]=r[r[id]];
		//cout<<l[id]<<' '<<pre[id]<<endl;
	}
	ll ans=0;
	for(int i=1;i<=n;++i)
	{
		if(al[i]==ma) continue;
//		cout<<pre[i]<<' '<<nxt[i]<<endl;
		ans=max(ans,query(pre[i]+1,root[nxt[i]-1],al[i]));
	}
	printf("%d",ans);
}
int main()
{
	//ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	solve();
	return 0;
}

未完待续...

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值