#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;
}
[POJ 3580]Super Memo
這是一道非常好的題目,考察對於splay(或其他平衡樹)的綜合應用,需要注意的是splay 上浮 和 下沉 的實現,我的splay以自頂向下方式實現。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.