1. 程式人生 > >BZOJ 3223 淺談SPLAY伸展樹演算法區間翻轉

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

嗯,就是這樣