洛谷P3721 [AH2017/HNOI2017]单旋

题目描述

H国是一个热爱写代码的国家,那里的人们很小去学校学习写各种各样的数据结构。伸展树(splay)是一种数据结构,因为代码好写,功能多,效率高,掌握这种数据结构成为了H国的必修技能。有一天,邪恶的“卡”带着他的邪恶的“常数”来企图毁灭H国。“卡”给H国的人洗脑说,splay如果写成单旋的,将会更快。“卡”称“单旋splay”为“spaly”。虽说他说的很没道理,但还是有H国的人相信了,小H就是其中之一,spaly马上成为他的信仰。而H国的国王,自然不允许这样的风气蔓延,国王构造了一组数据,数据由m(不超过10^5)个操作构成,他知道这样的数据肯定打垮spaly,但是国王还有很多很多其他的事情要做,所以统计每个操作

所需要的实际代价的任务就交给你啦。数据中的操作分为5种:

  1. 插入操作:向当前非空spaly中插入一个关键码为key的新孤立节点。插入方法为,先让key和根比较,如果key比根小,则往左子树走,否则往右子树走,如此反复,直到某个时刻,key比当前子树根x小,而x的左子树为空,那就让key成为x的左孩子;或者key比当前子树根x大,而x的右子树为空,那就让key成为x的右孩子。该操作的代价为:插入后,key的深度。特别地,若树为空,则直接让新节点成为一个单个节点的树。(各节点关键码互不相等。对于“深度”的解释见末尾对spaly的描述。)

  2. 单旋最小值:将spaly中关键码最小的元素xmin单旋到根。操作代价为:单旋前xmin的深度。(对于单旋操作的解释见末尾对spaly的描述。)

  3. 单旋最大值:将spaly中关键码最大的元素xmax单旋到根。操作代价为:单旋前xmax的深度。

  4. 单旋删除最小值:先执行2号操作,然后把根删除。由于2号操作之后,根没有左子树,所以直接切断根和右子树的联系即可。(具体见样例解释)。操作代价同2号操作。

  5. 单旋删除最大值:先执行3号操作,然后把根删除。操作代价同3号操作。

对于不是H国的人,你可能需要了解一些spaly的知识,才能完成国王的任务:

  1. spaly是一棵二叉树,满足对于任意一个节点x,它如果有左孩子lx,那么lx的关键码小于x的关键码。如果有右孩子rx,那么rx的关键码大于x的关键码。

  2. 一个节点在spaly的深度定义为:从根节点到该节点的路径上一共有多少个节点(包括自己)。

  3. 单旋操作是对于一棵树上的节点x来说的。一开始,设f为x在树上的父亲。如果x为f的左孩子,那么执行zig(x)操作(如上图中,左边的树经过zig(x)变为了右边的树),否则执行zag(x)操作(在上图中,将右边的树经过zag(f)就变成了左边的树)。每当执行一次zig(x)或者zag(x),x的深度减小1,如此反复,直到x为根。总之,单旋x就是通过反复执行zig和zag将x变为根。

输入输出格式

输入格式:

输入文件名为 splay.in。

第一行单独一个正整数 m (1 <= m <= 10^5)。

接下来 m 行,每行描述一个操作:首先是一个操作编号 c( 1<=c<=5),既问题描述中给出的 5 种操作中的编号,若 c= 1,则再输入一个非负整数 key,表示新插入节点的关键码。

输出格式:

输出文件名为 splay.out。

输出共 m 行,每行一个整数,第 i 行对应第 i 个输入的操作的代价。

输入输出样例

输入样例#1: 
5
1 2
1 1
1 3
4 
5
输出样例#1: 
1 
2 
2
2 
2

说明

20%的数据满足: 1 <= m <= 1000。

另外 30%的数据满足: 不存在 4,5 操作。

100%的数据满足: 1<=m<=10^5; 1<=key<=10^9。 所有出现的关键码互不相同。 任何一个非插入操作,一定保证树非空。 在未执行任何操作之前,树为空。

裸的spaly肯定不能过100%。。。

那就记录spaly树的信息,然后全部丢进splay里。。。

插入

splay的插入不多说,但是spaly怎么插入呢?

我们发现,spaly插入过程一定经过其 前驱 和 后继,并且以其中深度较大的节点为父节点。

于是在splay中找到 前驱后继,判断一下,再连父子关系即可。

单旋最小值

在splay中找到最小值,转到根,并输出深度(深度在splay中单独维护,下面会讲)

而在spaly中,我们可以发现一个性质:

某节点(设为x)单旋到根,只有三对父子关系改变:

x的父节点变为0;x右孩子 的父亲改为 x 的父亲;spaly的根节点 的父亲改为 x。

然后再在splay中将x伸展到根,x的父亲伸展到x右儿子节点。

然后一波操作:

x的深度改为1,x在spaly中的父亲(即x在splay中的右儿子)深度+1,x在spaly中的父亲的左儿子深度不变,右儿子深度全部加1。

单旋最大值同理。

代码:见下方程序中 splay_min,splay_max。

删除最小值

先单旋最小值,然后我们会发现最小值节点无论在 splay 中还是在 spaly 中都只有 右儿子

那不就直接操作?

砍掉根节点与右儿子的连边,根节点右子树所有节点的深度全部加1,再把根节点赋为其右儿子即可。

删除最大值同理。

代码:见下方 work 中的 case 4 与case 5。

深度的维护

splay中维护的深度是此节点在 spaly 中的深度。

这时,如果子树全部加1,我们可以打上懒惰标记,然后在 旋转、伸展、求kth时全部pushdown一次即可。

记录spaly树信息

只要记录 父节点 与 左右儿子即可,不需要记录那么多。。。

如果认真看完了上面,你发现还是过不了,可能因为有许多细节需要处理,所以附上代码。。。

附代码:

#include<iostream>
#include<algorithm>
#include<cstdio>
#define MAXN 100010
using namespace std;
int n,m=0,size=1,root=0,head;
struct splay{
	int f,s,son[2];
	int v,c,key;
}a[MAXN];
struct spaly{
	int f,son[2];
}b[MAXN];
inline int read(){
	int date=0,w=1;char c=0;
	while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
	while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();}
	return date*w;
}
inline void clean(int rt){
	a[rt].son[0]=a[rt].son[1]=a[rt].f=a[rt].s=a[rt].v=a[rt].c=0;
	b[rt].f=b[rt].son[0]=b[rt].son[1]=0;
}
inline void pushup(int rt){
	if(!rt)return;
	a[rt].s=a[a[rt].son[0]].s+a[a[rt].son[1]].s+1;
}
inline void pushdown(int rt){
	if(!rt||!a[rt].c)return;
	if(a[rt].son[0]){
		a[a[rt].son[0]].c+=a[rt].c;
		a[a[rt].son[0]].v+=a[rt].c;
	}
	if(a[rt].son[1]){
		a[a[rt].son[1]].c+=a[rt].c;
		a[a[rt].son[1]].v+=a[rt].c;
	}
	a[rt].c=0;
}
inline void turn(int rt,int k){
	int x=a[rt].f,y=a[x].f;
	pushdown(x);pushdown(rt);
	a[x].son[k^1]=a[rt].son[k];
	if(a[rt].son[k])a[a[rt].son[k]].f=x;
	a[rt].f=y;
	if(y)a[y].son[a[y].son[1]==x]=rt;
	a[x].f=rt;
	a[rt].son[k]=x;
	pushup(x);pushup(rt);
}
void splay(int rt,int ancestry){
	while(a[rt].f!=ancestry){
		int x=a[rt].f,y=a[x].f;
		pushdown(y);pushdown(x);pushdown(rt);
		if(y==ancestry)turn(rt,a[x].son[0]==rt);
		else{
			int k=a[y].son[0]==x;
			if(a[x].son[k]==rt){turn(rt,k^1);turn(rt,k);}
			else{turn(x,k);turn(rt,k);}
		}
	}
	if(ancestry==0)root=rt;
}
inline int newnode(int fa,int key){
	int rt=size++;
	if(fa)a[fa].son[a[fa].key<key]=rt;
	a[rt].son[0]=a[rt].son[1]=0;
	a[rt].f=fa;a[rt].key=key;
	a[rt].s=1;
	return rt;
}
int insert(int rt,int key){
	int fa=0;
	while(rt){
		fa=rt;
		pushdown(rt);
		rt=a[rt].son[a[rt].key<key];
	}
	rt=newnode(fa,key);
	splay(rt,0);
	return rt;
}
void find(int rt,int x){
	if(!rt)return;
	while(a[rt].son[a[rt].key<x])rt=a[rt].son[a[rt].key<x];
	splay(rt,0);
}
int kth(int rt,int k){
	while(1){
		pushdown(rt);
		int y=a[rt].son[0];
		if(k>a[y].s+1){
			k-=a[y].s+1;
			rt=a[rt].son[1];
		}
		else if(k<=a[y].s)rt=y;
		else return rt;
	}
}
int front_next(int rt,int x,int k){
	find(rt,x);
	rt=root;
	if((a[rt].key>x&&k)||(a[rt].key<x&&k^1))return rt;
	rt=a[rt].son[k];
	while(a[rt].son[k^1])rt=a[rt].son[k^1];
	return rt;
}
int insert_spaly(int rt,int key){
	rt=insert(rt,key);
	int front=front_next(rt,key,0),next=front_next(rt,key,1),fa;
	if(a[front].v>a[next].v)fa=front;
	else fa=next;
	a[rt].v=a[fa].v+1;
	b[rt].f=fa;
	if(fa)b[fa].son[a[fa].key<key]=rt;
	return a[rt].v;
}
void splay_min(int rt,int k){
	rt=kth(rt,k);
	printf("%d\n",a[rt].v);
	splay(rt,0);
	if(b[rt].f)splay(b[rt].f,rt);
	a[rt].v=1;
	if(head!=rt){
		if(b[rt].son[1])b[b[rt].son[1]].f=b[rt].f;
		if(b[rt].f)b[b[rt].f].son[0]=b[rt].son[1];
		b[head].f=rt;
		b[rt].son[1]=head;
		b[rt].f=0;head=rt;
		if(!a[rt].son[1])return;
		int front=a[rt].son[1];
		a[front].v+=1;
		if(a[front].son[1]){
			a[a[front].son[1]].v+=1;
			a[a[front].son[1]].c+=1;
		}
	}
}
void splay_max(int rt,int k){
	rt=kth(rt,k);
	printf("%d\n",a[rt].v);
	splay(rt,0);
	if(b[rt].f)splay(b[rt].f,rt);
	a[rt].v=1;
	if(head!=rt){
		if(b[rt].son[0])b[b[rt].son[0]].f=b[rt].f;
		if(b[rt].f)b[b[rt].f].son[1]=b[rt].son[0];
		b[head].f=rt;
		b[rt].son[0]=head;
		b[rt].f=0;head=rt;
		if(!a[rt].son[0])return;
		int front=a[rt].son[0];
		a[front].v+=1;
		if(a[front].son[0]){
			a[a[front].son[0]].v+=1;
			a[a[front].son[0]].c+=1;
		}
	}
}
void work(){
	int f,x;
	n=read();
	for(int cases=1;cases<=n;cases++){
		f=read();
		switch(f){
			case 1:{
				x=read();
				if(m==0){
					head=root=newnode(0,x);
					a[root].v=1;
					printf("1\n");
				}
				else printf("%d\n",insert_spaly(root,x));
				m++;
				break;
			}
			case 2:{
				splay_min(root,1);
				break;
			}
			case 3:{
				splay_max(root,m);
				break;
			}
			case 4:{
				splay_min(root,1);
				m--;
				root=a[root].son[1];
				head=b[head].son[1];
				
				clean(a[root].f);
				
				a[root].f=b[head].f=0;
				if(root){
					a[root].v-=1;
					a[root].c-=1;
				}
				break;
			}
			case 5:{
				splay_max(root,m);
				m--;
				root=a[root].son[0];
				head=b[head].son[0];
				
				clean(a[root].f);
				
				a[root].f=b[head].f=0;
				if(root){
					a[root].v-=1;
					a[root].c-=1;
				}
				break;
			}
		}
	}
}
int main(){
	freopen("splay.in","r",stdin);
	freopen("splay.out","w",stdout);
	work();
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值