弱势无图解FHQ-Treap 及各种乱七八糟的错误方法

发布时间 2023-08-22 11:56:37作者: Juye_Scene

众所周知,FHQ-Treap是一个码量少,可区间翻转,能持久化的treap(只是常熟略大)
像我这种蒟蒻,想调处有旋Treap或者Splay肯定要个114514年

以下可能以FHQ代表FHQ-Treap(逃

前置芝士

treap的节点拥有两个权值,一个是值,一个是优先级。优先级满足堆的性质,值满足二叉搜索树的性质。
当两个权值相同时,treap是固定的。堆的性质以及随机的优先级可以让树深保持在\(\log n\),确保时间复杂度;而二叉搜索树可以带来求排名,前驱,后继等操作。

前置提醒

FHQ-Treap 的合并要求一个FHQ的值全部小于等于另一个,建议是确定一下哪个大,哪个小。这里以左小右大为例.

操作

1、Update

这个就是和线段树一模一样的Update,不保熟(

void Update(int pos)
{
	siz[pos]=cnt[pos]+siz[son[pos][0]]+siz[son[pos][1]]; return;
}

siz即FHQ的大小,son[pos][0]代表左儿子,son[pos][1]代表右儿子

2、Split

字面意思,将一个FHQ按权值\(val\)分成小于等于和大于两个。
我们将一个FHQ分成\(x\)\(y\)两个(即根节点为\(x\)\(y\)本文中左(即\(x\)FHQ)一律小于右(即\(y\)FHQ)

假设我们在原FHQ的节点\(pos\)

若该点的值大于\(val\)由二叉搜索树可知该点和它的右子树节点都大于\(val\),将其置入右(y)FHQ
并在左子树中寻找也大于\(val\)的子树,归入该点在\(y\)FHQ所在点的左子树中

若该点的值小于等于\(val\)由二叉搜索树可知该点和它的左子树节点都小于等于\(val\),将其置入左(x)FHQ
并在右子树中寻找也小于等于\(val\)的子树,归入该点在\(x\)FHQ所在点的右子树中
(可能有点绕)
回溯时记得及时在该点Update以保证子树大小正确。

\(pos\)为0,即空子树,便无法再分

void Split(int pos,int vlx,int &x,int &y)
{
	if(pos==0)
	{
		x=y=0;
		return;
	}
	if(val[pos]<=vlx) x=pos,Split(son[pos][1],vlx,son[pos][1],y);
	else y=pos,Split(son[pos][0],vlx,x,son[pos][0]);
	Update(pos);
}

3、Merge

将两个FHQ合并,要求一个FHQ的所有值都小于等于另一个。
由于值都是已经有大小的,我们可以将\(x\)置入\(y\)的左子树或将\(y\)置入\(x\)的右子树
但我们要使优先级满足堆的性质

再次假设我们在两个FHQ的节点\(x\)\(y\) 这里以小根堆为例

\(x\)的优先级大于\(y\)优先级,由堆可知\(x\)和它的子树节点的优先级都大于\(y\)的优先级,因为\(x\)的值小于等于\(y\),将其加在\(y\)的左子树下

\(x\)的优先级小于\(y\)优先级,由堆可知\(y\)和它的子树节点的优先级都大于\(x\)的优先级,因为\(y\)的值大于\(y\),将其加在\(x\)的右子树下

这里等于的条件归在哪都无所谓

回溯时记得及时在该点Update以保证子树大小正确。

\(x\),\(y\)有一个或多个为0,即存在空节点,直接返回另一个节点或0即可

int Merge(int x,int y)
{
	if(x==0||y==0)
	return x+y;
	if(rnd[x]>rnd[y])
	{
		son[y][0]=Merge(x,son[y][0]),Update(y); return y;
	}
	else
	{
		son[x][1]=Merge(son[x][1],y),Update(x); return x;
	}
}

4、Get

获得排名为\(vlx\)的树
本文将相同的点归在同一点,使用cnt记录
其实也可当作不同的点只需修改下Get和插入
这里其实和不同的BST一样
\(vlx\)小于等于左子树大小,则进入左子树
\(vlx\)大于左子树大小+该节点个数(即总个数-右子树大小),则减去左子树大小和该点的cnt,进入右子树
\(vlx\)大于左子树大小且小于等于左子树大小+该节点个数,该排名就是该节点的值,return即可

int Get(int pos,int vlx)
{
	if(vlx<siz[son[pos][0]]+1) return Get(son[pos][0],vlx);
	if(vlx>siz[son[pos][0]]+cnt[pos])
	return Get(son[pos][1],vlx-siz[son[pos][0]]-cnt[pos]);
	return val[pos];
}

以上的方法都是\(O(log n)\)的,每次都是进入左子树或右子树,总共不超过\(log n\)

接下来是不用递归的功能

1、插入

要插入\(vlx\)
现将原FHQ分裂出小于等于\(vlx\)\(x\)和大于的\(y\)
再从\(x\)中分出小于等于\(vlx\)\(x'\)和等于\(vlx\)\(z\)
\(z\)存在,给节点\(z\)的副本数和大小都加1
不存在,则新建一个\(z\)节点
再按顺序Merge回去

	Split(Root,vlx,x,y);
	Split(x,vlx-1,x,z);
	if(z==0) New(z,vlx);
	else cnt[z]++,siz[z]++;
	Root=Merge(Merge(x,z),y);

2、删除

说道删除,FHQ直接呵呵嗨
只需按插入的方法分出\(x\)
\(z\)存在且副本数大于一,给节点\(z\)的副本数和大小都减1
若副本数等于一,直接丢到垃圾桶里(
\(z\)不存在,???删不存在的数???

	Split(Root,vlx,x,y);
	Split(x,vlx-1,x,z);
	if(cnt[z]>1) cnt[z]--,siz[z]--;
	else z=0;
	Root=Merge(Merge(x,z),y);

3、查排名

\(vlx-1\)分裂出小于等于\(vlx-1\)\(x\)FHQ
答案即是\(x\)大小加1

	Split(Root,vlx-1,x,y);
	write(siz[x]+1),pc('\n');
	Root=Merge(x,y);

4、查排名对应的树

调用Get即可

	write(Get(Root,vlx)),pc('\n');

5、前驱

\(vlx-1\)分裂出小于等于\(vlx-1\)\(x\)FHQ
\(x\)中的最大值及是x中排名做后的
Get出\(x\)中排名为\(siz[x]\)的即可

	Split(Root,vlx-1,x,y);
	write(Get(x,siz[x])),pc('\n');
	Root=Merge(x,y);

6、后继

\(vlx\)分裂出大于\(vlx\)\(y\)FHQ
\(y\)中的最小值及是y中排名第一的
Get出\(x\)中排名为1的即可

	Split(Root,vlx,x,y);
	write(Get(y,1)),pc('\n');
	Root=Merge(x,y);

附上完整代码

点击查看代码
#include<bits/stdc++.h>
#define fo(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
#define Ts template<typename Ty,typename... Ar>
#define Tp template<typename Ty>
#define ll long long
#define RS register
#define gc getchar
#define pc putchar
#define I inline
using namespace std;
Tp I Ty wmax(Ty a,Ty b){return a>=b? a:b;}
Tp I Ty wmin(Ty a,Ty b){return a<=b? a:b;}
namespace WrongIO
{
	Tp I void read(Ty &x){x=0;Ty opt=1;char c=gc();while(!isdigit(c)&&c!='-')c=gc();if(c=='-')opt=-1,c=gc();while(isdigit(c))x=(x<<3)+(x<<1),x+=c-'0',c=gc();x*=opt;return;}
	Tp I void write(Ty x){short OI_USE[50],OI_top=0;if(x<=0) if(x==0)pc('0');else pc('-'),x*=-1;while(x)OI_USE[++OI_top]=x%10,x/=10;while(OI_top--)pc(OI_USE[OI_top+1]+'0');return;}
    I void writec(char c[]){int len=strlen(c);for(int i=0;i<len;i++)pc(c[i]);}
    I void writes(string s){int len=s.length();for(int i=0;i<len;i++)pc(s[i]);}
    I void readc(char &c,int l,int r){c=gc(); while(c!=EOF&&(c<l||c>r)) c=gc();}
    I void readc(char &c,char val){c=gc();while(c!=EOF&&c!=val) c=gc();}
    I void readc(char val){char c;c=gc();while(c!=EOF&&c!=val) c=gc();}
    I void readls(string &s){char c=gc();while(c!='\n') s.push_back(c),c=gc();}
    Ts I void read(Ty &x,Ar &...y) {read(x),read(y...);}
} using namespace WrongIO;
int val[100050],cnt[100050],rnd[100050],siz[100050],son[100050][2],ct;
int n,Root;
void Update(int pos)
{
	siz[pos]=cnt[pos]+siz[son[pos][0]]+siz[son[pos][1]]; return;
}
void New(int &id,int vlx)
{
	id=++ct;
	val[id]=vlx;
	rnd[id]=rand();
	siz[id]=cnt[id]=1;
	return;
}
int Merge(int x,int y)
{
	if(x==0||y==0)
	return x+y;
	if(rnd[x]>rnd[y])
	{
		son[y][0]=Merge(x,son[y][0]),Update(y); return y;
	}
	else
	{
		son[x][1]=Merge(son[x][1],y),Update(x); return x;
	}
}
void Split(int pos,int vlx,int &x,int &y)
{
	if(pos==0)
	{
		x=y=0;
		return;
	}
	if(val[pos]<=vlx) x=pos,Split(son[pos][1],vlx,son[pos][1],y);
	else y=pos,Split(son[pos][0],vlx,x,son[pos][0]);
	Update(pos);
}
int Get(int pos,int vlx)
{
	if(vlx<siz[son[pos][0]]+1) return Get(son[pos][0],vlx);
	if(vlx>siz[son[pos][0]]+cnt[pos])
	return Get(son[pos][1],vlx-siz[son[pos][0]]-cnt[pos]);
	return val[pos];
}
int main(){
	//fo("input5");
	read(n);
	for(int i=1;i<=n;i++)
	{
		int opt,vlx,x,y,z; read(opt,vlx);
		if(opt==1)
		{
			Split(Root,vlx,x,y);
			Split(x,vlx-1,x,z);
			if(z==0) New(z,vlx);
			else cnt[z]++,siz[z]++;
			Root=Merge(Merge(x,z),y);
		}
		if(opt==2)
		{
			Split(Root,vlx,x,y);
			Split(x,vlx-1,x,z);
			if(cnt[z]>1) cnt[z]--,siz[z]--;
			else z=0;
			Root=Merge(Merge(x,z),y);
		}
		if(opt==3)
		{
			Split(Root,vlx-1,x,y);
			write(siz[x]+1),pc('\n');
			Root=Merge(x,y);
		}
		if(opt==4)
		{
			write(Get(Root,vlx)),pc('\n');
		}
		if(opt==5)
		{
			Split(Root,vlx-1,x,y);
			write(Get(x,siz[x])),pc('\n');
			Root=Merge(x,y);
		}
		if(opt==6)
		{
			Split(Root,vlx,x,y);
			write(Get(y,1)),pc('\n');
			Root=Merge(x,y);
		}
	}
    return 0;
}//555,卡芙卡的池子歪了一个最不想要的姬子

雄氏老方,专治各种疑难杂症

1、FHQ-Treap真的短,好多都压成了一行写,导致Merge函数,忘写返回值了(
2、若将相同点合成一个点,在副本数加1的同时一定要给大小也加1,这个更新不到,减1同理
3、一定要明确两个FHQ的大小关系,不然会有奇怪的事发生
4、分裂中的两个分支容易写错,可能会出现死循环或导致死循环,发生MLE或TLE
5、一些地方要以值-1来分裂,要注意
6、Merge和Split在pos为0时记得退出
7-113、留给评论区
114、宇宙射线照射导致的UKE,雄氏老方也治不了(