[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;
}
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章