POJ3580[memo] 旋转吧,splay!

8 篇文章 0 订阅
2 篇文章 0 订阅

重新写了一下splay,并且琢磨自己的版。

以前看过这道题,感觉操作好繁琐,不做。

今天看了一下,恩,有必要再写一下splay,先拿这道题练手。


题意:

给定序列,维护6个操作:

ADD l r x  把[l,r]加上x

REVERSE l r  把[l,r]翻转

REVOLVE l r t 把[l,r]这一段向后滚动t次,  比如1,2,3  滚动2次变成  2,3,1

INSERT x y  在x后面插入y

DELTEL x删掉第x个元素

MIN l r 询问[l,r]中的最小值


对于第一个操作,用lazy标记维护pushdown一下就好了

对于第二个操作,用bool 型的rev维护pushdown一下就好了

第三个操作,先将t对区间长度取mod,然后分成前面的区间和后t个元素的区间,交换这两个区间的先后,我们可以先把两个区间分别翻转,再把整个区间翻转,这样影响的只是前后两个区间的位置,不影响里面的元素排列。


第四个操作,把x旋到根,x+1旋到根的右儿子,root->r->l一定为空,直接插入元素即可


第五个操作,把x-1旋到根,x+1旋到根的右儿子,root->r-l就是x,删掉就好了


第6个,在splay维护一个最小值,每次pushdown和rotate的时候更新一下,到时候照样的l-1旋到根,r+1选到根的右边,查询root->r-l即可。


PS:注意如果splay单纯地维护[1,n],对于l-1,r+1可能会越界,需要特判.

但是特判是在是麻烦,代码量增长不少,所以为了简便,我提前插入了0和n+1两个节点,之后涉及到区间的都把区间标号+1  


然后就没有了,我debug还是调了那么久,毕竟数据结构的题嘛,不过要比我想象的顺利一些。


PS被坑点:

1.DEL之后将空节点选到了根(果断RE,都空了,咋旋)   

2.REVOLVE那里开始写的特别复杂,后来改成这样,不过把又把区间标号向右移了1,算上翻转那里又+1,就把区间标号向右移了2次。


splay的性质是,把操作过的点旋到根,这样在一种玄学的推理下单词查询复杂度降为均摊logn .  所以我把insert和min涉及到的点旋到了根,应该会快些吧- -



然后就附上代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<cmath>
using namespace std;
const int maxn=100000+20;
struct node
{
	node *f;
	node *ch[2];
	int sz,key,m;//size key min
	int lazy;//addd
	bool rev;//reverse
	void rs()
	{
		sz=1+ch[0]->sz+ch[1]->sz;
		m=key;
		m=min(m,ch[0]->m);
		m=min(m,ch[1]->m);
	}
}S[maxn<<2],*root,*null;
int ncnt;
int n,m;
void clear()
{
	ncnt=0;
	null=&S[ncnt];
	null->sz=0;
	null->key=0x3f3f3f3f;
	null->m=0x3f3f3f3f;
	null->lazy=null->rev=0;	
	root=null;
}
node *newnode(int key)
{
	node *p=&S[++ncnt];
	p->sz=1;
	p->key=p->m=key;
	p->lazy=p->rev=0;
	p->ch[0]=p->ch[1]=p->f=null;
	return p;
}
void pushdown(node *u)
{
	if(u==null)return ;
	if(u->lazy)
	{
		if(u->ch[0]!=null)
		{
			u->ch[0]->m+=u->lazy;
			u->ch[0]->key+=u->lazy;
			u->ch[0]->lazy+=u->lazy;
		}
		if(u->ch[1]!=null)
		{
			u->ch[1]->m+=u->lazy;
			u->ch[1]->key+=u->lazy;
			u->ch[1]->lazy+=u->lazy;
		}
		u->lazy=0;
	}
	if(u->rev)
	{
		swap(u->ch[0],u->ch[1]);
		if(u->ch[0]!=null)u->ch[0]->rev^=1;
		if(u->ch[1]!=null)u->ch[1]->rev^=1;
		u->rev^=1;
	}
}
void rotate(node *u)
{
	node *f=u->f;
	if(f==null)return ;
	node *ff=f->f;
	pushdown(u);
	pushdown(f);
	int d=u==f->ch[1];
	int dd=(ff!=null)?(f==ff->ch[1]):0;
	
	if(u->ch[d^1]!=null)u->ch[d^1]->f=f;
	f->ch[d]=u->ch[d^1];
	
	u->ch[d^1]=f;
	f->f=u;
	
	if(ff!=null)ff->ch[dd]=u;
	u->f=ff;
	
	f->rs();
	u->rs();
}
void splay(node *u,node *p)
{
	pushdown(u);
	while(u->f!=p)
	{
		node *f=u->f;
		node *ff=f->f;
		if(ff==p)
		{
			rotate(u);
			break;
		}
		int d=u==f->ch[1];
		int dd=f==ff->ch[1];		
		if(d==dd)rotate(f);
		else rotate(u);
		rotate(u);
	}
	if(p==null)root=u;
	u->rs();
}
void insert(node *&u,node *&fa,int key)
{
	if(u==null)
	{
		u=newnode(key);
		u->f=fa;
		splay(u,null);
		return ;
	}
	insert(u->ch[1],u,key);
	u->rs();
}
char ss[20];
void find(int k,node *p)//找到排名为k的并旋到p下 
{
	node *u=root;
	while(1)
	{
		pushdown(u);
		if(u->ch[0]->sz+1==k)break;
		if(u->ch[0]->sz>=k)u=u->ch[0];
		else
		{
			k-=u->ch[0]->sz+1;
			u=u->ch[1];
		}
	}
	splay(u,p);
}
void Add(int l,int r,int x)//本来是要把l-1旋到根,r+1旋到
//根下,加了一个0,就是把l旋到根,r+2旋到根下 
{
	find(l,null);
	find(r+2,root);
	node *u=root->ch[1]->ch[0];
	u->lazy+=x;
	u->key+=x;
	u->m+=x;
	root->ch[1]->rs();
	root->rs();
}
void REVERSE(int l,int r)//本来是操作l-1,r+1选到根 
{
	find(l,null);
	find(r+2,root);
	node *u=root->ch[1]->ch[0];
	u->rev^=1;
} 
void REVOLVE(int l,int r,int x)
//对于[l,r]这段区间向右移t位,相当于把最后t位放到前面 
{
	int t=(x%(r-l+1)+(r-l+1))%(r-l+1);
	//分成两段[l,r-t]    [r-t+1,r] 
	REVERSE(l,r-t);
	REVERSE(r-t+1,r);
	REVERSE(l,r);
}
void INSERT(int l,int x)
{
	find(l+1,null);
	find(l+2,root);
	root->ch[1]->ch[0]=newnode(x);
	root->ch[1]->ch[0]->f=root->ch[1];
	root->ch[1]->rs();
	root->rs();
	splay(root->ch[1]->ch[0],null);
}
void DEL(int x)
{
	find(x,null);
	find(x+2,root);
	root->ch[1]->ch[0]=null;
	root->ch[1]->rs();
	root->rs();
}
void MIN(int l,int r)
{
	find(l,null);
	find(r+2,root);
	printf("%d\n",root->ch[1]->ch[0]->m);
	splay(root->ch[1]->ch[0],null);
}
int main()
{
	freopen("memo.in","r",stdin);
	freopen("memo.out","w",stdout);
	clear();
	scanf("%d",&n);
	insert(root,null,0);
	for(int i=1;i<=n;i++)
	{
		int x;
		scanf("%d",&x);
		insert(root,null,x);
	}
	insert(root,null,1000001);
	int l,r,x;
	scanf("%d",&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%s",ss);
		if(ss[0]=='A')
		{
			scanf("%d%d%d",&l,&r,&x);
			Add(l,r,x);
		}
		else if(ss[0]=='R'&&ss[3]=='E')
		{
			scanf("%d%d",&l,&r);
			REVERSE(l,r);
		}
		else if(ss[0]=='R'&&ss[3]=='O')
		{
			scanf("%d%d%d",&l,&r,&x);
			REVOLVE(l,r,x);
		}
		else if(ss[0]=='I')
		{
			scanf("%d%d",&l,&x);
			INSERT(l,x);
		}
		else if(ss[0]=='D')
		{
			scanf("%d",&x);
			DEL(x);
		}
		else
		{
			scanf("%d%d",&l,&r);
			MIN(l,r);
		}
	}
	return 0;
}

         

继续回顾splay

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值