FHQ-Treap(非旋treap/平衡树)——从入门到入坟

       作者:hsez_yyh

       链接: FHQ-Treap——从入门到入坟_hsez_yyh的博客-CSDN博客

       来源:湖北省黄石二中信息竞赛组
       著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

        平衡树有很多种,其中fhq-treap 可以说是最强的一种平衡树之一,它可以维护值域,也可以维护下标,还能维护区间修改,更难能可贵的是,它可以完成splay都完成不了的可持久化操作。其唯一弱于splay的就是在LCT上,splay维护LCT的复杂度是O(nlog(n)),而fhq-treap的复杂度为O(nlog^2(n)),稍微大了一点,但是比splay好写多了。

        其实FHQ-treap的实现非常简单,本人认为,数据结构其实都不难实现,只是难于在概念上抽象的理解,所以,对于学习数据结构的最好方法就是手动模拟,在开始我们愉快的FHQ-treap之旅之前,在此先瞻仰一下FHQ-treap的最先引入者范浩强大佬。

        现在我们正式开始(其实迫于教练压力才写的这篇文章)

        首先fhq-treap是一个二叉搜索树(BST),它的每个节点有两个主要信息:keyval,key是我们fhq-treap要维护的键值,而val是随机生成的(rand()),key信息主要用于我们对于题目信息的处理,而val则是用于维持fhq-treap在结构上满足期望高度为logn的,val保证了fhq-treap拥有稳定的logn的高度,不会像splay一样退化。fhq-treap 除了要满足关于key的BST性质之外,还需满足关于val的小根堆性质。就是说,对于任意fhq-treap中的节点 i ,其左子树上的所有节点的key小于等于 i 节点的key值,i 节点所有右子树上所有节点的key 大于等于 i 节点的key值;对于任意节点 i ,其左、右儿子的val值大于等于 i 的val值(满足小根堆的性质)。要牢牢记住这两点的区别,否则会像我一样搞混,然后写代码的时候狂 WA,(手动悲剧)。下面给出一颗fhq-treap:

        上图就是一颗fhq-treap,其中每个节点中的数字为val,每个节点下方的数字为当前节点的key       可以发现一颗fhq-treap中序遍历后得到的序列是单调递增(如果维护方式不同其也可以是单调递减的)的。 非常好的性质——神犇ljz说。 这里,我们要引入fhq-treap的两个基本操作:splitmerge, 其中split是分离操作,merge是合并操作,下面我们来一一阐明:

        1. split  通常情况下,split用于分离一颗fhq-treap,对于一个元素 x ,我们会将这棵fhq-treap分裂为左右两棵树,左树上每个节点的key值都小于等于(<=)x , 而右树上的所有节点的key值都大于 x ;下面给出代码:

void split(int pos,int xx,int &l,int &r)// pos是当前fhq-treap的根
{  // &l 和 &r分别是fhq-treap分裂后得到的两个fhq-treap的根,这里用指针(引用)的方式,便于传递值
	if(!pos)//如果该节点为空树,则分裂成的左右两个树也应为空
	{
		l=r=0;
		return ;
	}
	if(tr[pos].key<=xx)// 如果当前节点的key值小于等于xx,则证明该节点的左子树上所有的key值也小于等于xx
	{
		l=pos;//让该节点成为左树的节点
		split(tr[l].r,xx,tr[l].r,r);//继续递归分离当前节点的右子树
	}
	else//同上
	{
		r=pos;
		split(tr[r].l,xx,l,tr[r].l);//继续递归分离当前节点的左子树
	}
	push_up(pos);
    //一定要记得push_up,因为pos点的左右子树大小发生了改变,否则会导致信息不对
}
//以上便是递归分离左右子树的方法

         以上代码如果学过线段树的话会好理解的多,本蒟蒻也建议先去熟练掌握线段树再来学习fhq-treap,这样会轻松很多。下面我们来模拟这个过程——本蒟蒻就是开始没有自己去模拟,导致一直不是很理解fhq-treap的实现方式。

        对于上图,假如我们要将其分离为小于等于18和大于18两个部分:

        

   

  在此之后,我们会继续递归,分裂K节点的左子树,大家可以自己手动模拟下(主要是画图太麻烦了,QAQ),由于I节点的权值20>18,所以我们将 I 节点归入右fhq-treap,接下来我们会碰到空节点,递归将会跳出,这时,我们就能得到左右两棵fhq-treap了:

以上就是fhq-treap的基本操作之一split操作的全部过程了 。

        2. merge 操作  这是一个合并操作,但它也不能随便合并,如果要合并两棵fhq-treap,那么它们可以进行合并当且仅当左fhq-treap上所有节点的key值都小于等于右fhq-treap中的最小key值(也就是左树小于等于右树)或两棵树中有空树。 这个性质很重要,为根据val合并两棵树做好的前提,保证了BST结构不会因合并而被破坏。 合并时,我们会根据左右两棵树的根节点的val值大小进行合并,大家先看一下代码:

int merge(int l,int r)//传入左右子树的根节点
{
	if(!l||!r) //如果有空树
	return l|r;//直接返回非空树(本蒟蒻这样的写法等同于l+r,是用来装杯的)
	if(tr[l].val<=tr[r].val)
	{
        //如果左树上当前节点的val值小于右树上当前节点的val值
        //将l变为合并后的新fhq-treap的根节点
		tr[l].r=merge(tr[l].r,r);//继续递归确定l的右子树该接谁
		push_up(l);//一定记得上传
		return l;//返回新得到的根节点
	}
	else// 同上
	{
		tr[r].l=merge(l,tr[r].l);
		push_up(r);
		return r;
	}
}

        还是一样,画图好理解:

以上是两棵要合并的fhq-treap

 

 以上就是fhq-treap合并的全部过程了。

        到现在,fhq-treap的最基本也是精华所在你已经完全掌握了,下面我们来看一道题目:

【模板】普通平衡树 - 洛谷     这道题就是平衡树的板子题,用fhq-treap可以轻松切掉,代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,idx;
int dl,dr,temp,rt;
struct node
{
	int l,r;
	int key,val;
	int si;//以当前节点为根的树的节点数
}tr[N*2];
int get_rand(int xx)
{
	tr[++idx].key=xx;
	tr[idx].val=rand();
	tr[idx].si=1;
	return idx;
} //建立一个新节点
void push_up(int pos)
{
	tr[pos].si=tr[tr[pos].l].si+tr[tr[pos].r].si+1;
    // 注意这里,这里跟线段树的push_up不一样,要记得加上自己的大小
}
void split(int pos,int xx,int &l,int &r)//分离操作
{
	if(!pos)
	{
		l=r=0;
		return ;
	}
	if(tr[pos].key<=xx)
	{
		l=pos;
		split(tr[l].r,xx,tr[l].r,r);
	}
	else
	{
		r=pos;
		split(tr[r].l,xx,l,tr[r].l);
	}
	push_up(pos);
}
int merge(int l,int r) // 合并操作
{
	if(!l||!r)
	return l|r;
	if(tr[l].val<=tr[r].val)
	{
		tr[l].r=merge(tr[l].r,r);
		push_up(l);
		return l;
	}
	else
	{
		tr[r].l=merge(l,tr[r].l);
		push_up(r);
		return r;
	}
}
void insert(int xx)
{
    // 对于合并操作,先将原树分裂成<=xx-1和 >xx-1(>=xx)两个部分
    // 将xx与<=xx-1的部分合并,再和>xx-1的部分合并
	split(rt,xx-1,dl,dr);
	rt=merge(merge(dl,get_rand(xx)),dr);
}
void delet(int xx)
{
	split(rt,xx-1,dl,dr);
	split(dr,xx,temp,dr);
    //连续两次分裂,temp得到了一棵所有key值==xx的fhq-treap
	temp=merge(tr[temp].l,tr[temp].r);
    //由于只能删一个xx,而原树中可能有很多xx,那么我们直接合并temp的左右子树即可
	rt=merge(dl,merge(temp,dr));
    // 将原树恢复(已删去xx)
}
int get_rk(int xx)
{
//非常简单,直接分离出<=xx-1的一棵树,也就是<xx的一棵树
//然后xx的排名就是<xx的树的大小+1即可
	split(rt,xx-1,dl,dr);
	int rnk=tr[dl].si+1;
	rt=merge(dl,dr);
	return rnk;
}
int get_num(int pos,int xx)
{
//这里采用递归搜索,思想见二分搜索树
	int u=tr[tr[pos].l].si+1;
	if(u==xx)
	return tr[pos].key;
	if(u>xx) return get_num(tr[pos].l,xx);
	else return get_num(tr[pos].r,xx-u);
}
int main()
{
	scanf("%d",&n);
	int op,xx;
	while(n--)
	{
		scanf("%d %d",&op,&xx);
		if(op==1) insert(xx);
		else if(op==2) delet(xx);
		else if(op==3) printf("%d\n",get_rk(xx));
		else if(op==4) printf("%d\n",get_num(rt,xx));
		else if(op==5)
		{
			split(rt,xx-1,dl,dr);
			printf("%d\n",get_num(dl,tr[dl].si));
            // 前驱就是<=xx-1的这棵树中排名最后的那个数
			rt=merge(dl,dr);
		}
		else 
		{
			split(rt,xx,dl,dr);
			printf("%d\n",get_num(dr,1));
            //后继就是>xx的这棵树中排名第一的树    
			rt=merge(dl,dr);
		}
	}
	return 0;
}

        好了,目前为止,平衡树维护值域的方法你就已经掌握了,下面我们再来看一个fhq-treap维护序列的题目:

【模板】文艺平衡树 - 洛谷 这道题就是典型的fhq-treap维护序列,操作也非常简单,仅仅是加上了一个翻转的懒标记而已——可见学线段树的必要。

        值得一说的是,fhq-treap在维护序列时,void split(int pos,int k,int &l,int &r)的意思不再是分离<=k和>k的两棵树,而是变为了 将序列pos 的前k个元素分离出来形成一棵新fhq-treap l,另外的一部分形成另一颗 fhq-treap r .

         下面展示代码: 

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m;
struct node
{
	int l,r;
	int key,val;
	int si;
	bool tag;
}tr[N*2];
int dl,dr,temp,rt,idx;
int get_rand(int xx)
{
	tr[++idx].key=xx;
	tr[idx].val=rand();
	tr[idx].si=1;
	return idx;
}
void push_up(int pos)
{
	tr[pos].si=tr[tr[pos].l].si+tr[tr[pos].r].si+1;
}
void push_down(int pos)
{
    //下传懒标记,如果一直下传下去,将会实现左右区间翻转
	if(tr[pos].tag)
	{
		tr[tr[pos].l].tag^=1;
		tr[tr[pos].r].tag^=1;
		swap(tr[pos].l,tr[pos].r);
		tr[pos].tag=0;
	}
}
void split(int pos,int k,int &l,int &r)
{
	if(!pos)
	{
		l=r=0;
		return ;
	}
    //只要对某个节点的子节点做操作,就一定要将该节点的标记下传
	push_down(pos);
	int u=tr[tr[pos].l].si+1;
	if(u<=k)
	{
		l=pos;
		split(tr[pos].r,k-u,tr[pos].r,r);
		push_up(l);
	}
	else
	{
		r=pos;
		split(tr[pos].l,k,l,tr[pos].l);
		push_up(r);
	}
}
int merge(int l,int r)
{
	if(!l||!r) return l|r;
	push_down(l);
	push_down(r);
	if(tr[l].val<=tr[r].val)
	{
		tr[l].r=merge(tr[l].r,r);
		push_up(l);
		return l;
	}
	else
	{
		tr[r].l=merge(l,tr[r].l);
		push_up(r);
		return r;
	}
}
void reverse(int l,int r)
{
	int p1=0,p2=0,p3=0,p4=0;
	split(rt,l-1,p1,p2);
	split(p2,(r-l+1),p3,p4);
    // 将要翻转的一段分离出来,打上标记,再合并回去
	tr[p3].tag^=1;
	p2=merge(p3,p4);
	rt=merge(p1,p2);
}
void print_res(int pos)
{
	push_down(pos);
	if(tr[pos].l) print_res(tr[pos].l);
	printf("%d ",tr[pos].key);
	if(tr[pos].r) print_res(tr[pos].r);
    // 迭代输出,没什么好说的
}
int main()
{
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		split(rt,i-1,dl,dr);
		rt=merge(merge(dl,get_rand(i)),dr);
	}
	int xx,yy;
	while(m--)
	{
		scanf("%d %d",&xx,&yy);
		reverse(xx,yy);
	}
	print_res(rt);
	return 0;
}

        到此,平衡树最大的两个功能都能实现了 Orz ! ! !

        但既然要入坟,那么在此推荐两道题目: 

         266. 超级备忘录 - AcWing题库

         [NOI2005] 维护数列 - 洛谷                祝大家WA开心,本蒟蒻虽然过了,但是懒得贴代码了——都是泪

        你以为就这样结束了??? 不,还远远不止,既然要入坟,那么就要坚持到底:

fhq-treap之所以是现在OI界地位逐渐超过splay的结构,自然有splay做不到的地方。众所周知 ,splay依靠二次旋转来保持logn的复杂度的,然而,这样做会经常改变树的结构,自然,splay对于处理可持久化的信息是一点办法都没有——就是嘛,转来转去的一看就不好  。但是嘛,splay可以做的fhq-treap可以做,splay不能做的fhq-treap还可以做(就算lct复杂度高一点,但是也是可以做的嘛,手动滑稽) 。

        口胡完毕,进入正题: 细心的你可以发现,在fhq-treap的 split 操作和 merge 操作时,由于fhq-treap的期望高度是 logn,并且可以发现 split 和 merge 时都是自上而下的,那么,对于每次 split 和 merge 操作,真正有所变动的点只有 logn 个  ,所以我们只需要在操作过程中记录下沿途经过的 logn 个节点,就能对某次历史版本有所记录,其记录方式与主席树类似——又是线段树 。

如果再仔细思考,我们可以发现,每次 merge 操作都是合并先由 split 操作分离的两棵树,所以,对于真正结果有影响的操作只有 split ,所以,我们只需要在每次 split 的时候记录沿途的节点即可。

例题: 【模板】可持久化平衡树 - 洛谷

需要注意的是,我们每次操作产生的新版本,都要创建一个新根来指向次版本。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=5e5+10;
int n,dl,dr,temp;
struct node
{
	int l,r;
	LL key,val;
	LL si;
}tr[N*60];
int rt[N*60],idx;
void push_up(int pos)
{
	tr[pos].si=tr[tr[pos].l].si+tr[tr[pos].r].si+1;
 } 
int get_rand(int xx)
{
	tr[++idx].key=xx;
	tr[idx].val=rand();
	tr[idx].si=1;
	return idx;
}
void split(int pos,int xx,int &l,int &r)
{
	if(!pos)
	{
		l=r=0;
		return ;
	}
	if(tr[pos].key<=xx)
	{
		l=++idx;
		tr[l]=tr[pos];
		split(tr[l].r,xx,tr[l].r,r);
		push_up(l);
	}
	else
	{
		r=++idx;
		tr[r]=tr[pos];
		split(tr[pos].l,xx,l,tr[r].l);
		push_up(r);
	}
}
int merge(int l,int r)
{
	if(!l||!r) return l|r;
	int pos=++idx;
	if(tr[l].val<=tr[r].val)
	{
		tr[pos]=tr[l];
		tr[pos].r=merge(tr[pos].r,r);
	}
	else
	{
		tr[pos]=tr[r];
		tr[pos].l=merge(l,tr[pos].l);
	}
	push_up(pos);
	return pos;
}
void insert(int &pos,int xx)
{
	split(pos,xx-1,dl,dr);
	pos=merge(merge(dl,get_rand(xx)),dr);
}
void delet(int &pos,int xx)
{
	split(pos,xx-1,dl,dr);
	split(dr,xx,temp,dr);
	temp=merge(tr[temp].l,tr[temp].r);
	pos=merge(dl,merge(temp,dr));
}
int get_rk(int &pos,int xx)
{
	split(pos,xx-1,dl,dr);
	int rk=tr[dl].si+1;
	pos=merge(dl,dr);
	return rk;
}
int get_num(int pos,int xx)
{
	int u=tr[tr[pos].l].si+1;
	if(u==xx) return tr[pos].key;
	if(u>xx) return get_num(tr[pos].l,xx);
	else return get_num(tr[pos].r,xx-u);
}
int main()
{
	scanf("%d",&n);
	int op,xx,t;
	int cnt=0;
	while(n--)
	{
		cnt++;
		scanf("%d %d %d",&t,&op,&xx);
		rt[cnt]=rt[t];
		if(op==1) insert(rt[cnt],xx);
		else if(op==2) delet(rt[cnt],xx);
		else if(op==3) printf("%d\n",get_rk(rt[cnt],xx));
		else if(op==4) printf("%d\n",get_num(rt[cnt],xx));
		else if(op==5)
		{
			split(rt[cnt],xx-1,dl,dr);
			printf("%d\n",get_num(dl,tr[dl].si));
			rt[cnt]=merge(dl,dr);
		}
		else
		{
			split(rt[cnt],xx,dl,dr);
			printf("%d\n",get_num(dr,1));
			rt[cnt]=merge(dl,dr);
		}
	}
	return 0;
}

        既然有了这道题,那么你肯定能想到另外一道:【模板】可持久化文艺平衡树 - 洛谷

其实和普通的文艺平衡树一样,只需要多加一个可持久化操作即可,代码如下(也就写了一小时):

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=2e5+10;
int n,idx;
int dl,dr,temp;
struct node
{
	int l,r;
	int key,val;
	int si;
	bool rev;
	LL sum;
}tr[N*100];
int rt[N];
void push_up(int pos)
{
	tr[pos].si=tr[tr[pos].l].si+tr[tr[pos].r].si+1;
	tr[pos].sum=tr[tr[pos].l].sum+tr[tr[pos].r].sum+tr[pos].key;
}
int get_rand(int xx)
{
	tr[++idx].sum=xx;
	tr[idx].key=xx;
	tr[idx].si=1;
	tr[idx].val=rand();
	return idx;
}
void push_down(int &pos)
{
	if(!tr[pos].rev) return ;
	if(tr[pos].l)
	{
		tr[++idx]=tr[tr[pos].l];
		tr[pos].l=idx;
		tr[tr[pos].l].rev^=1;
	}
	if(tr[pos].r)
	{
		tr[++idx]=tr[tr[pos].r];
		tr[pos].r=idx;
		tr[tr[pos].r].rev^=1;
	}
	tr[pos].rev=0;
	swap(tr[pos].l,tr[pos].r);
}
void split(int pos,int k,int &l,int &r)
{
	if(!pos)
	{
		l=r=0;
		return ;
	}
	push_down(pos);
	int u=tr[tr[pos].l].si+1;
	if(u<=k)
	{
		l=++idx;
		tr[l]=tr[pos];
		split(tr[pos].r,k-u,tr[l].r,r);
		push_up(l);
	}
	else
	{
		r=++idx;
		tr[r]=tr[pos];
		split(tr[pos].l,k,l,tr[r].l);
		push_up(r);
	}
}
int merge(int l,int r)
{
	if(!l||!r) return l|r;
	int pos=++idx;
	if(tr[l].val<=tr[r].val)
	{
		tr[pos]=tr[l];
		push_down(pos);
		tr[pos].r=merge(tr[pos].r,r);
		push_up(pos);
		return pos;
	}
	else
	{
		tr[pos]=tr[r];
		push_down(pos);
		tr[pos].l=merge(l,tr[pos].l);
		push_up(pos);
		return pos;
	}
}
void insert(int &pos,int xx,int yy)
{
	split(pos,xx,dl,dr);
	dl=merge(dl,get_rand(yy));
	pos=merge(dl,dr);
}
void delet(int &pos,int xx)
{
	split(pos,xx-1,dl,dr);
	split(dr,1,temp,dr);
	pos=merge(dl,dr);
}
void reverse(int &pos,int l,int r)
{
	split(pos,l-1,dl,dr);
    split(dr,r-l+1,temp,dr);
    tr[temp].rev^=1;
    dr=merge(temp,dr);
    pos=merge(dl,dr);
}
LL query(int &pos,int l,int r)
{
	split(pos,l-1,dl,dr);
    split(dr,r-l+1,temp,dr);
    LL ans=tr[temp].sum;
    dr=merge(temp,dr);
    pos=merge(dl,dr);
    return ans;
}
int main()
{
	scanf("%d",&n);
	int op,v,xx,yy;
	LL lan=0;
	for(int t=1;t<=n;t++)
	{
		scanf("%d %d",&v,&op);
		rt[t]=rt[v];
		if(op==2)
		{
			scanf("%d",&xx);
			xx^=lan;
			delet(rt[t],xx);
		}
		else
		{
			scanf("%d %d",&xx,&yy);
			if(op==1)
			{
				xx^=lan,yy^=lan;
				insert(rt[t],xx,yy);
			}
			else if(op==3)
			{
				xx^=lan,yy^=lan;
				reverse(rt[t],xx,yy);
			}
			else
			{
				xx^=lan,yy^=lan;
				lan=query(rt[t],xx,yy);
				printf("%lld\n",lan);
			}
		}
	}
	return 0;
}

        一定要注意的是,每次操作,不管是合并、分离还是push_down,只要影响到树形和树中节点的信息,就必须新建节点来储存,所以fhq-treap的可持久化格外的费空间,不过现在空间都给的足,所以一般都能过。用fhq-Treap支持区间翻转和可持久化,需要注意的是和区间修改主席树一样,在push_down翻转rev的时候需要复制左右儿子结点,把rev推到新复制的结点上。

        多体会下,就能很容易理解啦,在本蒟蒻看来,其实平衡树比线段树简单多了 qwq

        好了,现在已经快入坟了,就差轻轻一推了,那么它来啦!!! 作为平衡树五题的压轴题目,也是平衡树的另一大考点:【模板】二逼平衡树(树套树) - 洛谷         相信有人已经看不下去了,本蒟蒻也是到这里才体会到了平衡树的快乐 ,当本蒟蒻听到树套树之后,直接兴奋的半个小时交了一发,然后快乐的只过了一个点。在听完课后,虽然本蒟蒻会写了,但是,但是!,又交了一发,只过了90分(关键是就我第一次提交过的那个点没过——离离原上谱) ,虽然后来我看题解用树状数组套平衡树过了,However,这和我线段树套平衡树的初衷不相符,所以这里就不贴代码,就稍微解释一下吧(尬)。

        对于此题,我们可以发现,如果单纯的求前驱和后继,排名,数值,用一棵fhq-treap来维护绰绰有余,然而,此题异常毒瘤,它让我们在一个区间中进行上述操作。区间? 这不又是线段树嘛,欸,猜对了。又题目的提示可以知道:我们要在一棵维护区间的线段树上做平衡树,也就是下标线段树套值域平衡树,线段树的每个节点都是一段区间,所以我们要在线段树的每个节点上开一个平衡树,这样就可以轻松地维护信息啦——但是,这样也会有问题,我们在查询排名为k的数时,由于答案需要多棵不可合并的平衡树一起提供,所以我们要在线段树二分区间(logn)的前提下在平衡树中二分答案(logn*logn),那么复杂度就会到达 log^3(n),这样又会被出题人卡死。怎么办呢,为了降低复杂度,我们可以利用线段树的二分性,把树套树改成 下标平衡树套值域线段树,这样复杂度就会将下来,具体实现推荐去看肖然大佬的blog 【树套树】 - 肖然 的博客 - 洛谷博客

        本蒟蒻就水到这了,反正我是已经入坟躺平了,希望各位神犇大佬AC顺利 Orz

  • 18
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
可持久化splay是一种数据结构,它是对splay树进行修改和查询的一种扩展。在传统的splay树中,对树的修改操作会破坏原有的树结构,而可持久化splay树则允许我们对树进行修改、查询,并且可以保存修改后的每个版本的树结构。 在可持久化splay树中,我们不会直接对原树进行修改,而是通过复制每个节点来创建新的版本。这样,每个版本都可以独立地修改和查询,保留了原有版本的结构和状态。每个节点保存了其左子树和右子树的引用,使得可以在不破坏原有版本的情况下进行修改和查询。 为了实现可持久化splay树,我们可以使用一些技巧,比如引用中提到的哨兵节点和假的父节点和孩子节点。这些技巧可以帮助我们处理根节点的旋转和其他操作。 此外,可持久化splay树还可以与其他数据结构相结合,比如引用中提到的可持久化线段树。这种结合可以帮助我们解决更复杂的问题,比如区间修改和区间查询等。 对于可持久化splay树的学习过程,可以按照以下步骤进行: 1. 理解splay树的基本原理和操作,包括旋转、插入、删除和查找等。 2. 学习如何构建可持久化splay树,包括复制节点、更新版本和保存历史版本等。 3. 掌握可持久化splay树的常见应用场景,比如区间修改和区间查询等。 4. 深入了解与可持久化splay树相关的其他数据结构算法,比如可持久化线段树等。 在解决问题时,可以使用二分法来确定答案,一般称为二分答案。通过对答案进行二分,然后对每个答案进行检查,以确定最终的结果。这种方法可以应用于很多问题,比如引用中提到的在线询问问题。 综上所述,可持久化splay是一种对splay树进行修改和查询的扩展,可以通过复制节点来创建新的版本,并且可以与其他数据结构相结合解决更复杂的问题。学习过程中可以按照一定的步骤进行,并且可以使用二分法来解决一些特定的问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [[学习笔记]FHQ-Treap及其可持久化](https://blog.csdn.net/weixin_34283445/article/details/93207491)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [可持久化数据结构学习笔记](https://blog.csdn.net/weixin_30376083/article/details/99902410)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值