[poj3580]SuperMemo(splay终结题)

这是一道关于Splay树应用的题目,涉及区间数字操作,包括加法、翻转、旋转、插入、删除和查询最小值。通过建立二叉搜索树并维护lazy标记来处理区间操作,对于特殊操作如区间旋转,转换为交换两个区间来实现。
摘要由CSDN通过智能技术生成

传送门


题意:
你需要维护一组数字,包括这样的几个操作:
给出一个数字序列,有6种操作:
1、 ADD l r d:区间[l,r]的数都加上d。
2、 REVERSE l r : 将区间[l,r]中的数翻转 。
3、 REVOLVE l r t :将区间[l,r]旋转t次,如1 2 3 4 5 旋转2次后就变成4 5 1 2 3 。
4、 INSERT p x :在第p个数后面插入x 。
5、DELETE p :删除第p个数 。
6、 MIN l r : 查询区间[x,y]中的最小值 。


一道不错的splay练手题。
首先用类似线段树的中序遍历建树保证二叉搜索树性质。

对于所有的区间[l,r]操作,可以把l-1旋转到根,r+1旋转到l-1的下面,那么由于二叉搜索树的性质,这时候r+1的左子树就是[l,r]区间。

那么对于区间加法,我们需要维护一个lazy的加法标记,在旋转的时候Pushdown传递标记给左右孩子(与线段树相同),对于区间翻转,由于二叉搜索树的性质,我们在翻转某一棵树的时候只要把他所有的子树的左右孩子翻转一下就好了,那么一样维护一个lazy的旋转标记即可。

对于在p后插入x,我们模仿区间操作的方法,把p和p+1之间的空间“撑”开来。也就是说把p翻转到根,把p+1旋转到p的下面,那么p+1的左子树就空出来了,就可以将新节点插入进去。

对于删除p,我们同样把p-1旋转到根,把p+1旋转到p-1的下面,那么p+1的左子树就是p,直接删掉即可,空间蛮大的这时候最好回收空间。

区间最小值很简单,我们维护一个最小值mn,在Pushup的时候和子树大小size一起被左右孩子更新即可。

接下来剩下的操作就是这题的特殊操作,区间旋转,我们不难发现,因为旋转[l,r]区间t次是往后面推t个数然后放到前面,所以区旋转实际上是交换两个区间。那么我们把旋转[l,r]区间t次可以看做交换[l,r-t]和[r-t+1,r],具体的操作也是区间操作,先把后面被推出去的那段拿出来,放到前面即可。

注意,对于任何涉及区间操作的题目都要设置哨兵节点。


#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long ll;
const int N=1e5+10;
const int INF=2147483647;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

struct splay
{
	int d,f,size,son[2];
	int fz,mn,add;
}tr[N*2]; int tot,root,n;
void newnode(int x,int d,int f)//新建节点,东西比较多就单放出来了 
{
	tr[x].f=f; tr[x].d=tr[x].mn=d;
	tr[x].size=1; 
	tr[x].fz=tr[x].add=0;
	tr[x].son[0]=tr[x].son[1]=0;
}
void delnode(int x)//删除节点 
{
	tr[x].f=tr[x].d=tr[x].size=tr[x].mn=0;
	tr[x].son[0]=tr[x].son[1]=tr[x].fz=tr[x].add=0;	
}
void update_add(int x,int d)//执行加 
{
	if(!x) return ;
	tr[x].add+=d; tr[x].d+=d; tr[x].mn+=d;
}
void update_fz(int x)//执行翻转 
{
	if(!x) return;
	swap(tr[x].son[0],tr[x].son[1]);
	tr[x].fz^=1;	
}
void pushup(int x)//向上更新size,min 
{
	if(!x) return ;
	int lc=tr[x].son[0],rc=tr[x].son[1];
	tr[x].size=1; tr[x].mn=tr[x].d;
	if(lc) tr[x].size+=tr[lc].size,tr[x].mn=min(tr[x].mn,tr[lc].mn);
	if(rc) tr[x].size+=tr[rc].size,tr[x].mn=min(tr[x].mn,tr[rc].mn);
}
void pushdown(int x)//lazy:更新下方add,fz 
{
	if(!x) return;
	if(tr[x].add)
	{
		update_add(tr[x].son[0],tr[x].add);
		update_add(tr[x].son[1],tr[x].add);
		tr[x].add=0;
	}
	if(tr[x].fz)
	{
		update_fz(tr[x].son[0]);
		update_fz(tr[x].son[1]);
		tr[x].fz=0;	
	}
}
void rotate(int x,int w)
{
	int f=tr[x].f,ff=tr[f].f;
	int r,R;
	
	pushdown(f); 
	pushdown(x);
	
	r=tr[x].son[w],R=f;
	tr[R].son[1-w]=r;
	if(r!=0) tr[r].f=R;
	
	r=x,R=ff;
	if(tr[ff].son[0]==f) tr[R].son[0]=r;
	else tr[R].son[1]=r;
	tr[r].f=R;
	
	r=f,R=x;
	tr[R].son[w]=r;
	tr[r].f=R;
	
	pushup(f);
	pushup(x);
}

void splay(int x,int rt)
{
	pushdown(x);
	while(tr[x].f!=rt)
	{
		int f=tr[x].f,ff=tr[f].f;
		pushdown(ff),pushdown(f),pushdown(x);
		if(ff==rt)
		{
			if(tr[f].son[0]==x) rotate(x,1);
			else rotate(x,0);
		}
		else 
		{
				 if(tr[ff].son[0]==f && tr[f].son[0]==x) rotate(f,1),rotate(x,1);
			else if(tr[ff].son[1]==f && tr[f].son[1]==x) rotate(f,0),rotate(x,0);
			else if(tr[ff].son[0]==f && tr[f].son[1]==x) rotate(x,0),rotate(x,1);
			else if(tr[ff].son[1]==f && tr[f].son[0]==x) rotate(x,1),rotate(x,0);
			//三点一线的情况不能单旋否则复杂度退化 
		}
	}
	pushup(x);
	if(rt==0) root=x;
}
int a[N];
void build(int &x,int l,int r,int f)
{ //中序遍历建树保证平衡 
	if(l>r) return;
	int mid=(l+r)/2;
	x=mid; newnode(x,a[x],f);
	build(tr[x].son[0],l,mid-1,x);
	build(tr[x].son[1],mid+1,r,x);
	pushup(x);
}

int find_kth(int x,int k)
{
    pushdown(x);
    if(tr[tr[x].son[0]].size+1 == k) return x;
    else if(tr[tr[x].son[0]].size >= k) return find_kth(tr[x].son[0],k);
    else return find_kth(tr[x].son[1], k-tr[tr[x].son[0]].size-1);
}

//--------- 
void add(int l,int r,int d) //[l~r]加d 
{
	int x=find_kth(root,l-1),y=find_kth(root,r+1);
	splay(x,0); 
	splay(y,x);
	update_add(tr[y].son[0],d);
}
void ins(int p,int x)//p后面插入x 
{
	int y=find_kth(root,p),z=find_kth(root,p+1);
	splay(y,0); splay(z,y);
	newnode(++tot,x,z);  tr[z].son[0]=tot;
	for(int i=z;i;i=tr[i].f) pushdown(i),pushup(i);
	splay(z,0);
}
void del(int p)//删除p 
{
	int x=find_kth(root,p-1),y=find_kth(root,p+1);
	splay(x,0); splay(y,x);
	delnode(tr[y].son[0]); tr[y].son[0]=0;
	pushup(y); pushup(x);
}
int get_min(int l,int r)//[l,r]间最小值 
{
	int x=find_kth(root,l-1),y=find_kth(root,r+1);
	splay(x,0); splay(y,x);
	return tr[tr[y].son[0]].mn;	
}
void rev(int l,int r)//翻转[l,r] 
{
	int x=find_kth(root,l-1),y=find_kth(root,r+1);
	splay(x,0); splay(y,x);
	update_fz(tr[y].son[0]);	
}
void exchange(int l1,int r1,int l2,int r2)//区间[l1,r1],[l2,r2]交换 
{
	int x=find_kth(root,l2-1),y=find_kth(root,r2+1);
	splay(x,0); splay(y,x); //导出区间
	int tmp=tr[y].son[0]; tr[y].son[0]=0; //剪贴 
	
	x=find_kth(root,l1-1),y=find_kth(root,l1);
	splay(x,0); splay(y,x);
	
	tr[y].son[0]=tmp;
	tr[tmp].f=y;
}
//---

char ss[10];
int main()
{
	scanf("%d",&n);
	a[1]=a[n+1]=INF;//设立哨兵节点 
	for(int i=2;i<=n+1;i++) scanf("%d",&a[i]);
	
	tot=n+2; root=0;
	tr[0].f=tr[0].size=tr[0].son[0]=tr[0].son[1]=tr[0].add=tr[0].fz=0;	
	tr[0].d=INF; 
	build(root,1,n+2,0);
	pushup(root);
	
	int m;scanf("%d",&m);
	int l,r,d; 
	while(m--)//由于有哨兵节点后面的位置都要+1 
	{
		scanf("%s",ss);
		if(ss[0]=='A')
		{
			l=read(),r=read(),d=read();
			add(l+1,r+1,d);
		}
		else if(ss[0]=='I')
		{
			l=read(),d=read();
			ins(l+1,d);	
		}
		else if(ss[0]=='D')
		{
			d=read();
			del(d+1);	
		}
		else if(ss[0]=='M')
		{
			l=read(),r=read();
			printf("%d\n",get_min(l+1,r+1));	
		}
		else if(ss[0]=='R' && ss[3]=='E')
		{
			l=read(),r=read();
			rev(l+1,r+1);	
		}
		else if(ss[0]=='R' && ss[3]=='O')
		{
			l=read(),r=read(),d=read();
			d%=(r-l+1);
			if(d) exchange(l+1,r-d+1,r-d+1+1,r+1);	
		}
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值