Luogu P3369 【模板】普通平衡树

1 篇文章 0 订阅
0 篇文章 0 订阅
Luogu P3369 【模板】普通平衡树

啊 splay啊

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>

//左儿子一定比我小 右儿子一定比我大 
/* 		  * <----0
          | 
          * <----rt 
       /   \
      *    *
     / \  / \
    *  * *   *   这是一棵树 
*/  

struct nod1{int ch[2],c,fa,size,cnt;}tr[110010];
//ch[0]是左儿子编号 ch[1]是右儿子比那好 
//c是节点数值 fa是节点爸爸 size是以节点为根子树大小  
//cnt是和节点数值一样的数的个数 
int n,rt,tot;
//n是操作数 rt是根 tot是整个数的节点个数,添加新节点时用到 

void update(int x)//更新维护 此处x是编号 
{
	tr[x].size=tr[tr[x].ch[0]].size+tr[x].cnt+tr[tr[x].ch[1]].size;
	//x子树大小=x左子树+x右子树+x节点相同数个数 
	return ;
}//ok

int findip(int x)//找x数值的节点编号 此处x是数值 
{
	int now=rt;//从根开始找 
	while(x!=tr[now].c)
	{
		if(x<tr[now].c)//比当前点数值小 
		{
			if(tr[now].ch[0]==0)break;//没有,只好跳出循环 返回当前这个now 是与x最贴切的节点 
			now=tr[now].ch[0];//走左边 
		}
		else
		{
			if(tr[now].ch[1]==0)break;
			now=tr[now].ch[1];//走右边 
		}
	}
	return now;
}

void add(int c,int x)//添加新节点 此处c是数值,x是新节点爸爸的编号  
{
	tot++;
	tr[tot].c=c;tr[tot].cnt=1;
	tr[tot].size=1;tr[tot].fa=x;
	tr[tot].ch[0]=tr[tot].ch[1]=0;
	if(c<tr[x].c)tr[x].ch[0]=tot;
	else tr[x].ch[1]=tot;//判断新节点是爸爸的左儿子还是右儿子 
	return ;
}

void rotate(int x,int w)//单旋 此处x是编号 w是儿子标记 w等于1意味着我是我爸的左儿子,否则为我爸的右儿子
{
	int f=tr[x].fa,ff=tr[f].fa;
	tr[f].ch[1-w]=tr[x].ch[w];
	//这里的ch[0]是左儿子,ch[1]是右儿子
	//如果我是我爸的右儿子,那么说明我的整棵子树都比他大,所以我的左儿子给我爸当右儿子
	//如果我是我爸的左儿子,那么说明我的整棵子树都比他小,所以我的右儿子给我爸当左儿子
	if(tr[x].ch[w]!=0)tr[tr[x].ch[w]].fa=f; 
	if(tr[ff].ch[0]==f)tr[ff].ch[0]=x;//爷爷>爸爸 
	else tr[ff].ch[1]=x;//爸爸是爷爷什么儿子 我旋转后就给爷爷当什么儿子 
	tr[x].fa=ff;
	tr[f].fa=x;
	tr[x].ch[w]=f;
	update(f);//更新底层 
	update(x);//更新当前层 
}

void splay(int x,int goal)//把x旋到目标节点下面 
{
	while(tr[x].fa!=goal)
	{
		int f=tr[x].fa,ff=tr[f].fa;
		if(ff==goal)//爷爷就是目标
		{//旋一次就行 
			if(x==tr[f].ch[0])//我<我爸 
				rotate(x,1);
			else rotate(x,0);
		} 
		else
		{
			if(tr[ff].ch[0]==f)//爷爷>我爸 
			{
				if(tr[f].ch[0]==x)//我爸>我 
				{//我与我爸共线 则双旋
					rotate(f,1);//我爸是我爷爷左儿子 
					rotate(x,1);//我是我爸左儿子 
					 
				}
				else
				{//我爸<我 
					rotate(x,0);//我是我爸右儿子 
					rotate(x,1);//现在我是我爷爷左儿子
				}
			} 
			else//爷爷<我爸 
			{ 
				if(tr[f].ch[0]==x)
				{
					rotate(x,1);//我是我爸左儿子 
					rotate(x,0);//现在我是我爷爷右儿子 
				}
				else
				{//我与我爸共线 则双旋
					rotate(f,0);//我爸是我爷爷右儿子 
					rotate(x,0);//我是我爸右儿子 
				}
			}
		} 
	}
	if(goal==0)rt=x;
}

void insert(int x)//插入一个数 此处x是数值 
{
	if(rt==0)
	{//第一个节点 先让你当0儿子 
		add(x,0);
		rt=tot;
		return ;
	}
	int ip=findip(x);//找出x数值的节点编号或是最贴近的节点 
	if(tr[ip].c==x)
	{//之前插入过这个数值 
		tr[ip].cnt++;
		update(ip);
		splay(ip,0);
	}
	else
	{
		add(x,ip);//新点 
		update(ip);
		splay(tot,0);
	}
}

void del(int x)//删除一个数 此处x是数值 
{
	int ip=findip(x);//拿它编号 
	splay(ip,0);//让它当根 方便操作 
	if(tr[ip].c!=x)return ;//之前没有插入过这个数值 
	if(tr[ip].cnt>1)
	{//不止一个这个数值 
		tr[ip].cnt--;
		update(ip);
	}
	else if(tr[ip].ch[0]==0&&tr[ip].ch[1]==0)//已经将这个点旋到根了 如果没有左右儿子 说明整棵树只有一个点 
		rt=0,tot=0;
	else if(tr[ip].ch[0]!=0&&tr[ip].ch[1]==0)//只有一个儿子就让他继承 
		rt=tr[ip].ch[0],tr[tr[ip].ch[0]].fa=0;
	else if(tr[ip].ch[1]!=0&&tr[ip].ch[0]==0)//只有一个儿子就让他继承 
		rt=tr[ip].ch[1],tr[tr[ip].ch[1]].fa=0;
	else
	{
		int p=tr[ip].ch[0];
		while(tr[p].ch[1]!=0)p=tr[p].ch[1];//找前驱来 
		splay(p,ip);//旋到我这里来,因为前驱是小于我中最大的 它此时没有右儿子 
		rt=p;tr[p].fa=0;
		tr[tr[ip].ch[1]].fa=p;//直接把右儿子给它 
		tr[p].ch[1]=tr[ip].ch[1];
		update(p);
	}
}

int rank(int x)//此处x是数值 找出x的排名 
{
	int ip=findip(x);splay(ip,0);//把我旋成根 方便处理 
	return tr[tr[ip].ch[0]].size+1;//我是根 所以左子树全小于我 +1就是我排名 
}

int kth(int x)//找排名是x的数值 
{
	int now=rt;
	while(1)
	{
		if(x<=tr[tr[now].ch[0]].size)//如果我小于左边的个数 去左边找 
			now=tr[now].ch[0];
		else if(tr[tr[now].ch[0]].size+tr[now].cnt<x)
		{//如果比左+当前自己还大 去右边 
			x=x-tr[tr[now].ch[0]].size-tr[now].cnt;
			now=tr[now].ch[1];
		} 
		else break;//等于当前 
	}
	return tr[now].c;
}

int last(int x)//找前驱 此处x是数值 
{
	int ip=findip(x);
	splay(ip,0);
	if(x<=tr[ip].c&&tr[ip].ch[0]!=0)
	{
		ip=tr[ip].ch[0];
		while(tr[ip].ch[1]!=0)ip=tr[ip].ch[1];
	} //比我小最大的:左子树最右的 
	if(x<=tr[ip].c)return 0;
	return tr[ip].c;
}

int next(int x)//找后继 此处x是数值 
{
	int ip=findip(x);
	splay(ip,0);
	if(x>=tr[ip].c&&tr[ip].ch[1]!=0)
	{
		ip=tr[ip].ch[1];
		while(tr[ip].ch[0]!=0)ip=tr[ip].ch[0]; 
	} //比我大最小的:右子树最左边
	if(x>=tr[ip].c)return 0;
	return tr[ip].c;
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		int h,x;
		scanf("%d %d",&h,&x);
		if(h==1)
		insert(x);
		else if(h==2)
			del(x);
		else if(h==3)
			printf("%d\n",rank(x));
		else if(h==4)
			printf("%d\n",kth(x));
		else if(h==5)
			printf("%d\n",last(x));
		else if(h==6)
			printf("%d\n",next(x));
	}
} 

看了好多博客 要昏了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值