[POJ 3580]Super Memo

这是一道非常好的题目,考察对于splay(或其他平衡树)的综合应用,需要注意的是splay 上浮 和 下沉 的实现,我的splay以自顶向下方式实现。
#include <iostream>
#include <cstdio>
using namespace std;
const int inf=~0U>>2;
int a[200010],lazy[200010],mi[200010],size[200010];
bool rev[200010];
int l[200010],r[200010];
int pl[200010],pr[200010];
int n,m,i,root,x,y,z;
void add(int x,int y)//给x为根的子树添加大小为y的增量标记
{
    if(!x||!y) return; 
    lazy[x]+=y; 
	a[x]+=y; 
	mi[x]+=y;
}
inline void push_down(int x)
{
    if(!x) return;
    add(l[x],lazy[x]);
    add(r[x],lazy[x]);
    if(rev[x])
    {
        swap(l[x],r[x]);
        if(l[x]) rev[l[x]]^=1;
        if(r[x]) rev[r[x]]^=1;
    }
    rev[x]=lazy[x]=0;
}
inline void update(int x)
{
    size[x]=size[l[x]]+size[r[x]]+1;
    mi[x]=min(a[x],min(mi[l[x]],mi[r[x]]));
}
void zig(int &x)
{
    int rc=r[x];
    r[x]=l[rc];
    l[rc]=x;
    update(x);
    x=rc;
}
void zag(int &x)
{
    int lc=l[x];
    l[x]=r[lc];
    r[lc]=x;
    update(x);
    x=lc;
}
void splay(int &x,int y)//在以x为根的子树中寻找第y个数,并将其伸展到该子树的根 
{
    if(!x) return;
    pl[0]=pr[0]=0;//左树和右树 
    for(;;)
    {
        push_down(x);
		push_down(l[x]);
		push_down(r[x]);//下传标记 
        int temp=size[l[x]]+1;
        if(y==temp||(y<temp&&!l[x])||(y>temp&&!r[x])) 
			break;//已经找到 
        if(y<temp)//在左子树 
        {
            if(l[l[x]]&&y<=size[l[l[x]]]) 
				zag(x);
   			pr[++pr[0]]=x; 
			x=l[x];//连接到左树 
  		}
  		else//在右子树 
  		{
   			y-=temp;
   			temp=size[l[r[x]]]+1;
   			if(r[r[x]]&&y>temp) 
			{
				y-=temp; 
				zig(x);
			}
			pl[++pl[0]]=x; 
			x=r[x];//连接到右树 
  		}
 	}
 	pl[++pl[0]]=l[x]; 
	pr[++pr[0]]=r[x];//组合左中右树 
    for(int i=pl[0]-1;i>0;i--) 
	{
		r[pl[i]]=pl[i+1]; 
		update(pl[i]);
	}//从下往上更新左树信息 
    for(int i=pr[0]-1;i>0;i--) 
	{
		l[pr[i]]=pr[i+1]; 
		update(pr[i]);
	}//从下往上更新右树信息 
 	l[x]=pl[1]; 
	r[x]=pr[1]; 
	update(x);//组合左中右树,更新根信息 
}
void ADD(int x,int y,int z)//增加操作,伸展y+1到根、x-1到根的左子节点,则x-1的右子树就代表[x,y],对其添加增量标记即可 
{
	splay(root,y+1);
	splay(l[root],x-1);
	add(r[l[root]],z);
}
void INSERT(int x,int y)//插入操作, 伸展x到根,在x和x的右子节点之间插入新节点y 
{
    splay(root,x);
 	a[++n]=y; 
	r[n]=r[root]; 
	r[root]=n;
 	update(n); 
	update(root);
}
void DELETE(int x)//删除操作,伸展x到根、x+1到根的右子节点,直接将x+1作为新根 
{
	splay(root,x);
	splay(r[root],1);
	l[r[root]]=l[root]; 
	root=r[root];
	update(root);
}
void REVERSE(int x,int y)//翻转操作,伸展y+1到根、x-1到根的左子节点,则x-1的右子树就代表[x,y],对其添加翻转标记即可 
{
	splay(root,y+1);
	splay(l[root],x-1);
	rev[r[l[root]]]^=1;
}
void REVOLVE(int x,int y,int z)//滚动操作,相当于交换区间[a,b]和[b+1,c] 
{
	z%=y-x+1;
	if(!z) return;
	int mid=y-z;
	splay(root,mid);
	splay(l[root],x-1);
	splay(r[root],y-size[l[root]]);
	z=l[root];
	l[root]=r[z];
	r[z]=l[r[root]];
	l[r[root]]=0;
	update(z); 
	update(r[root]); 
	update(root);
	splay(root,1);
	l[root]=z;
	update(root);
}
void MIN(int x,int y)//求最小值,伸展y+1到根、x-1到根的左子节点,则x-1的右子树就代表[x,y],输出其最小值即可 
{
	splay(root,y+1);
	splay(l[root],x-1);
	printf("%d\n",mi[r[l[root]]]);
}
int main()
{
	char str[10];
	cin>>n;
	size[1]=1; 
	l[n+2]=n+1; 
	size[n+2]=n+2; //初始化,1为极小点,n+2为极大点防止越界 
	mi[1]=mi[n+2]=mi[1]=mi[0]=inf;
	for(i=2;i<=n+1;i++)//a[i+1]代表位置i 
	{
		scanf("%d",&a[i]);
		l[i]=i-1; 
		size[i]=i;
		mi[i]=min(mi[i-1],a[i]);
	}
	mi[n+2]=mi[n+1];
	root=n=n+2;//根 
	cin>>m;
	for(i=0;i<m;i++)
	{
		scanf("%s",&str);
		if(str[0]=='A') 
		{
			scanf("%d%d%d",&x,&y,&z); 
			ADD(++x,++y,z);
		}
		if(str[0]=='I') 
		{
			scanf("%d%d",&x,&y); 
			INSERT(++x,y);
		}
		if(str[0]=='D') 
		{
			scanf("%d",&x); 
			DELETE(++x);
		}
		if(str[0]=='M') 
		{
			scanf("%d%d",&x,&y); 
			MIN(++x,++y);
		}  
		if(str[0]=='R'&&str[3]=='E') 
		{
			scanf("%d%d",&x,&y); 
			REVERSE(++x,++y);
		}
		if(str[0]=='R'&&str[3]=='O') 
		{
			scanf("%d%d%d",&x,&y,&z); 
			REVOLVE(++x,++y,z);
		}  
	}
	return 0;
}
 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值