自学FHQ-treap的草稿

更新:能过模板题(和加强版)的代码:

普通平衡树:

(请自行实现读入和输出函数)

点击查看代码
#include <iostream>
#include <random>
#include <time.h>
const int MAXN=1e6+50;
std::mt19937 Rand(time(NULL));
int tot; 
int Size[MAXN];
int Val[MAXN],Price[MAXN];
int Son[MAXN][2];
int New(int NewVal)
{
	Size[++tot]=1;
	Val[tot]=NewVal;
	Price[tot]=Rand();
	return tot;
}
void PushUp(int u)
{
	Size[u]=Size[Son[u][0]]+Size[Son[u][1]]+1;
}
void Split(int u,int k,int &Root1,int &Root2)//默认按值小于等于k分裂 
{
	if(!u)
	{
		Root1=Root2=0;
		return;
	}
	if(Val[u]<=k)
	{
		Root1=u;
		Split(Son[u][1],k,Son[u][1],Root2);//因为Root1=u了,所以Son[u]也必须在左树内 
	}
	else
	{
		Root2=u;
		Split(Son[u][0],k,Root1,Son[u][0]);//Root2=u,所有Son[u]必须在右树内 
	}
	PushUp(u);
}
int Merge(int x,int y)
{
	if(!x||!y)
	{
		return x|y;
	}
	if(Price[x]<=Price[y])
	{
		Son[x][1]=Merge(Son[x][1],y);
		PushUp(x);
		return x;
	}
	else
	{
		Son[y][0]=Merge(x,Son[y][0]);
		PushUp(y);
		return y;
	}
}
int GetKth(int u,int k)//从小到大排名 
{
	if(Size[u]==1)
	return Val[u];
	if(Size[Son[u][0]]>=k)
	return GetKth(Son[u][0],k);
	k-=Size[Son[u][0]];
	if(k==1)
	return Val[u];
	else
	return GetKth(Son[u][1],k-1);
}
int GetMax(int u)
{
	if(Size[u]==1)
	return Val[u];
	if(Son[u][1]!=0)
	return GetMax(Son[u][1]);
	return Val[u];
}
int GetMin(int u)
{
	if(Size[u]==1)
	return Val[u];
	if(Son[u][0]!=0)
	return GetMin(Son[u][0]);
	return Val[u];
}
int N;
int Root;
int main()
{
	read(N);
	int opt,x,Root1,Root2,Root3;
	while(N--)
	{
		read(opt);
		read(x);
		if(opt==1)
		{
			Split(Root,x,Root1,Root2);
			Root=Merge(Merge(Root1,New(x)),Root2);
		}
		if(opt==2)
		{
			Split(Root,x,Root2,Root3);
			Split(Root2,x-1,Root1,Root2);
			Root2=Merge(Son[Root2][0],Son[Root2][1]);
			Root=Merge(Merge(Root1,Root2),Root3);
		}
		if(opt==3)
		{
			Split(Root,x-1,Root1,Root2);
			print(Size[Root1]+1);
			putchar('\n');
			Root=Merge(Root1,Root2);
		}
		if(opt==4)
		{
			print(GetKth(Root,x));
			putchar('\n');
		}
		if(opt==5)
		{
			Split(Root,x-1,Root1,Root2);
			print(GetMax(Root1));
			putchar('\n');
			Root=Merge(Root1,Root2);
		}
		if(opt==6)
		{
			Split(Root,x,Root1,Root2);
			print(GetMin(Root2));
			putchar('\n');
			Root=Merge(Root1,Root2);
		}
	}
}

文艺平衡树:

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN=1e6+50;
mt19937 Rand(time(NULL));
int tot; 
int Size[MAXN];
int Val[MAXN],Price[MAXN];
int Son[MAXN][2];
int New(int NewVal)
{
	Size[++tot]=1;
	Val[tot]=NewVal;
	Price[tot]=Rand();
	return tot;
}
void PushUp(int u)
{
	Size[u]=Size[Son[u][0]]+Size[Son[u][1]]+1;
}
int Reverse[MAXN];
void PushDown(int u)
{
	if(Reverse[u])
	{
		swap(Son[u][0],Son[u][1]);
		Reverse[Son[u][0]]^=1;
		Reverse[Son[u][1]]^=1;
		Reverse[u]=0;
	}
}
void Split(int u,int k,int &Root1,int &Root2)
{
	if(!u)
	{
		Root1=Root2=0;
		return;
	}
	PushDown(u);
	if(k>Size[Son[u][0]])
	{
		Root1=u;
		Split(Son[u][1],k-Size[Son[u][0]]-1,Son[u][1],Root2);
	}
	else
	{
		Root2=u;
		Split(Son[u][0],k,Root1,Son[u][0]);
	}
	PushUp(u);
}
int Merge(int x,int y)
{
	if(!x||!y)
	{
		return x|y;
	}
	PushDown(x);
	PushDown(y);
	if(Price[x]<=Price[y])
	{
		Son[x][1]=Merge(Son[x][1],y);
		PushUp(x);
		return x;
	}
	else
	{
		Son[y][0]=Merge(x,Son[y][0]);
		PushUp(y);
		return y;
	}
}
int GetKth(int u,int k)//从小到大排名 
{
	PushDown(u);
	if(Size[u]==1)
	return Val[u];
	if(Size[Son[u][0]]>=k)
	return GetKth(Son[u][0],k);
	k-=Size[Son[u][0]];
	if(k==1)
	return Val[u];
	else
	return GetKth(Son[u][1],k-1);
}
int N,M;
int Root;
int main()
{
	scanf("%d%d",&N,&M);
	for(int i=1;i<=N;i++)
	{
		Root=Merge(Root,New(i));
	}
	while(M--)
	{
		int l,r;
		scanf("%d%d",&l,&r);
		int Root1,Root2,Root3;
		Split(Root,l-1,Root1,Root3);
		Split(Root3,r-l+1,Root2,Root3);
		Reverse[Root2]^=1;
		Root=Merge(Merge(Root1,Root2),Root3);
	}
	for(int i=1;i<=N;i++)
	{
		printf("%d ",GetKth(Root,i));
	}
}

(过几天改成正经博客)
每个点有权值和随机权值
权值满足二叉搜索树性质
随机权值满足堆性质
Size[i]:i的子树大小
Val[i]:i的值
Price[i]:i的随机权值,rand()
Son[i][0/1]:儿子

merge(x,y):合并以x为根的子树和以y为根的子树
x子树的最大值小于y子树的最小值,即y永远放右子树,x永远放左子树

split(u,k,Root1,Root2):把u按k(权值比k小、比k大 或 中序遍历前k个值、后面的值)分成左Root1,右Root2两棵树
考虑分成权值比k小,对于当前的u, Val[u]<=k,u和左子树全都给Root1,递归到右子树,反之亦然

对于插入,删除,把整棵树分成左,输入的Val(需要插入或删除)为根的那棵树,右,即先分成比Val小和比Val大,如果插入就新建一个节点然后跟左右2个区间merge,如果删除就再把比Val小的树split成比Val-1小的树和为Val的树 ,因为删除只需要删除一个(可能有多个值),所以把=Val的这棵树的根左右儿子合并起来再把这3个区间合并

第k大:常规查
Val是第几大:split成小于(因为相同不算)Val的树和大于等于Val的树,rank为小于Val的树的Size+1

Val的前驱:split成<=Val的树和>Val的树,找<=Val的树的最大值(一直右走)
后继:同理

每次split和merge都要pushup:Size、其它要维护的东西

区间翻转:打个标记,每次要递归的时候判断当前点有没有标记,有就下传,然后交换左右儿子。

mt19937:好用的随机数

mt19937 Rand(time(NULL));

做了题才发现,平衡树只能维护一个维度。比如,我想维护区间翻转,就得维护下标,从而没办法查第k 大,但是能查下标为 k 的数,反之也是如此。

再深入一下——所有数据结构都是维护一个维度,但一堆数据结构合起来,就能维护很多维度了。比如,普通树状数组可以维护一个维度的信息,CDQ 可以维护一个维度的信息,最开始再排个序,消除一个维度的影响,就可以做三维偏序了。

时间复杂度跟所需要维护的信息量有关,信息量是固定的,而最少都需要一个 log 来维护,就是跟计算信息熵的那个 log 一个意思。

posted @ 2022-09-13 22:02  0htoAi  阅读(31)  评论(0编辑  收藏  举报