BZOJ 3223 淺談SPLAY伸展樹演算法區間翻轉
世界真的很大
SPLAY是個很厲害的資料結構,相對於其他平衡樹,比如treap,對於每一次查詢雖然常數偏大,但是卻能力保總複雜度趨近於nlogn,而且除了treap能支援的操作以外,還支援如區間翻轉一類的特殊操作
其關鍵在於其獨屬的SPLAY操作
所以這次更多的是對SPLAY的一個詳細總結
好像也是tarjan發明的,orz
看題先:
description:
您需要寫一種資料結構(可參考題目標題),來維護一個有序數列,其中需要提供以下操作:翻轉一個區間,例如原有序序列是5 4 3 2 1,翻轉區間是[2,4]的話,結果是5 2 3 4 1
input
第一行為n,m n表示初始序列有n個數,這個序列依次是(1,2……n-1,n) m表示翻轉操作次數
接下來m行每行兩個數[l,r] 資料保證 1< =l<=r<=n
output
輸出一行n個數字,表示原始序列經過m次變換後的結果
題面還是比較良心的
看上去只需要一種操作,就是區間翻轉,但是其他資料結構好像不是很好維護這個操作
首先談談SPLAY是一個什麼東西
SPLAY首先是一顆BST,它有著BST共有的性質,即滿足左小右大的性質,而旋轉是BST幾乎共有的一種操作,旨在不破壞BST性質的情況下調整樹的結構
中序遍歷是一種樹的遍歷方式,只要樹是BST,那中序遍歷的節點順序就不會改變。因為中序遍歷是左中右的順序,而BST滿足左小右大的性質,對於任意一個點,比他小的點在不修改的前提下是固定的,所以中序遍歷的結果也不會改變
SPLAY的核心操作就是SPLAY,它的意思是將某一個節點通過旋轉的方式,不破壞BST,到達指定位置。但指定位置一定得在它上面。這麼做的目的是使得被多次訪問的點儘可能的靠近根節點,這樣在後續訪問過程中使得訪問儘量短,雖然每次訪問會多了將訪問節點旋轉到根的時間,但卻使後續對相同節點的訪問時間縮短,從而使時間複雜度“平攤”,確保總操作複雜度趨於穩定。
而實現的方法其實也相對簡單,就是旋轉
如果目標節點就是當前節點的父親,那直接旋轉便是
否則採用雙旋的辦法,往上跳。
雙旋右分2種
一種是當前節點的父親,和當前節點父親的父親,也就是爺爺,三者形成了鏈式結構,就是說父親是爺爺的左兒子,兒子也是父親的做兒子,或者父親是爺爺的右兒子,兒子也是父親的右兒子,這樣的,我們就先對父親進行旋轉,再旋轉兒子
另一種是之字型結構,就是父親是爺爺的左兒子,兒子是父親的右兒子,或者父親是爺爺的右兒子,兒子是父親的左兒子,這樣,就連續兩下把兒子轉上去
至於為什麼不一樣我也不是很懂,ZJC大神說鏈式結構連續單旋會被卡~~~~
再有就是其獨特的區間翻轉的性質。首先要實現區間翻轉的話,就要明確是對什麼建的一顆BST,並不是區間裡的值,而是區間的下標。在轉來轉去的過程中,需要保證中序遍歷一定是按區間下標12345。。。排好了的。
區間翻轉餓第一步是找到需要翻轉的區間,比如[L,R],這裡的L也好,R也好,都是指區間的下標,而由於是對區間下標建的BST,所以要查下標對應的節點的位置是很容易的。而這裡我們需要找到的是L-1和R+1的位置,通過SPLAY的方式將L-1節點轉到根節點,R+1節點轉到根節點的右子樹,因為L-R下標一定比L-1大,這又是一顆BST,所以L-R區間一定在L-1,就是根節點的右邊,同理一定在R+1,即根節點的右子樹的左邊,所以L-R對應的位置只能是且一定在根節點右兒子的左子樹。
當然SPLAY只能把節點轉到其祖先的位置,而如果L-1和R+1都在根節點的右子樹或左子樹怎麼辦哪
其實先SPLAY(L-1) 時,就已經使R+1在根節點的右邊了。。。
這時L-1就是新的根了,而L,R就在根節點右子樹的左子樹。這時我們就使從根節點右兒子的左兒子的兩個兒子開始,交換左右兒子,在逐層交換左右兒子
想想為什麼這樣是對的
假設總區間是1-10,要反轉的區間是3-8,模擬一下SPLAY過程,則現在根節點是2,其右兒子是9,而右兒子的左兒子是5,而其子樹就是3-8的區間了,5的左邊是1-4,右邊是6-8,交換左右兒子後就使得1-4到了右邊,而6-8到了左邊,是符合翻轉定義的。
而考慮下一層,3的右邊是4,而翻轉後應該4在3的左邊,交換左右子樹。
考慮這樣遞迴下去,類似於歸併排序的思路,每個區間逐層翻轉然後合併,而翻轉前挨在一起的數翻轉後必然還是挨在一起,所以是可以區間分治然後合併的
當然我們並不需要每次翻轉就直接翻到底,不然成本太高,而是打一個標記,類似於線段樹,在查詢的時候在隨著查詢的深入而隨便翻轉
大概也就是這些了
完整程式碼:
#include<stdio.h>
#include<algorithm>
using namespace std;
struct node
{
int sum,flag,siz;
node *ch[2],*fa;
void pushdown(node *nd)
{
if(flag)
{
swap(ch[0],ch[1]);
if(ch[0]!=nd) ch[0]->flag^=1;
if(ch[1]!=nd) ch[1]->flag^=1 ;
flag=0;
}
}
void update()
{
siz=ch[0]->siz+ch[1]->siz+1;
}
}pool[4000010],*tail=pool,*root,*null;
int n,m;
void init()
{
null=++tail;
null->siz=0;
null->ch[0]=null->ch[1]=null;
null->sum=null->flag=0;
}
node* newnode(node *fa)
{
node *nd=++tail;
nd->fa=fa;
nd->siz=1;
nd->ch[1]=nd->ch[0]=null;
nd->flag=0;
return nd;
}
void rot ( node*& x, int d )
{
node* y=x->fa;
y->ch[!d]=x->ch[d];
if(x->ch[d]!= null) x->ch[d]->fa=y ;
x->fa=y->fa ;
if(y->fa!=null)
(y==y->fa->ch[0]) ? y->fa->ch[0]=x : y->fa->ch[1]=x;
x->ch[d]=y;
y->fa =x;
x->update();
y->update();
}
node *build(node *fa,int lf,int rg)
{
if(lf>rg) return null;
node *nd=newnode(fa);
if(lf==rg)
{
nd->sum=lf;
return nd;
}
int mid=(lf+rg)>>1;
nd->sum=mid;
nd->ch[0]=build(nd,lf,mid-1);
nd->ch[1]=build(nd,mid+1,rg);
nd->update();
return nd;
}
void Splay(node *nd,node *tar)
{
while(nd->fa!=tar)
{
node *ne=nd->fa;
if(nd==ne->ch[0])
{
if(ne->fa!=tar&&ne==ne->fa->ch[0])
rot(ne,1);
rot(nd,1);
}
else
{
if(ne->fa!=tar&&ne==ne->fa->ch[1])
rot(ne,0);
rot(nd,0);
}
}
if(tar==null) root=nd;
}
node *kth(node *nd,int K)
{
nd->pushdown(null);
if(nd->ch[0]->siz+1==K) return nd;
if(nd->ch[0]->siz+1>K) return kth(nd->ch[0],K);
else return kth(nd->ch[1],K-nd->ch[0]->siz-1);
}
void rev(int L,int R)
{
node *x=kth(root,L);node *y=kth(root,R+2);
Splay(x,null);
Splay(y,root);
y->ch[0]->flag^=1;
}
void GrandOrder(node *nd)
{
if(nd==null) return ;
nd->pushdown(null);
GrandOrder(nd->ch[0]);
if(nd->sum>=1&&nd->sum<=n)
printf("%d ",nd->sum);
GrandOrder(nd->ch[1]);
}
int main()
{
init();
int L,R;
scanf("%d%d",&n,&m);
root=build(null,0,n+1);
while(m--)
{
scanf("%d%d",&L,&R);
rev(L,R);
}
GrandOrder(root);
return 0;
}
嗯,就是這樣