1. 程式人生 > >洛谷:P3384 【模板】文藝平衡樹(Splay)

洛谷:P3384 【模板】文藝平衡樹(Splay)

定位 描述 ever 論文 這樣的 紅黑樹 裏的 來源 分別是

原題地址:https://www.luogu.org/problemnew/show/P3391

題目簡述

您需要寫一種數據結構(可參考題目標題),來維護一個有序數列,其中需要提供以下操作:
翻轉一個區間,例如原有序序列是5 4 3 2 1,翻轉區間是[2,4]的話,結果是5 2 3 4 1


思路

首先明白Splay比起線段樹能多幹什麽:

  • 可以在一個有序序列中任意數後面動態插入一串數(不能比a後面一個數還大)
  • 可以刪除一段區間

可能描述不是很清楚,具體看這裏面給的論文鏈接:信息學競賽相關優秀文章合集
或者直接看這裏:運用伸展樹解決數列維護問題.pdf
如果搞不懂左旋右旋是什麽,可以先看信息學競賽相關優秀文章合集裏的AVL樹介紹。

對於AVL樹是一種為了防止樹結構不夠優導致深度過深時間復雜度退化,在保持二叉搜索樹性質不變的前提下進行的一種變換。簡單說就是把往一邊沈的樹弄的兩邊平衡些。
而在Splay中,將特定點旋轉到一定位置可以進行提取區間等操作,同時各種旋轉間接的使樹基本平衡(是的,可以構造數據卡掉。Treap樹對此表示同情)
下面兩幅圖應該有助於理解:
左旋(下面代碼裏的表達:把S往上轉一次)→技術分享圖片
右旋(下面代碼裏的表達:把E往上轉一次)→技術分享圖片
圖片來源:http://blog.csdn.net/sun_tttt/article/details/65445754
(文章是介紹紅黑樹的但是這個左旋右旋操作二叉搜索樹通用)
論文裏講的很詳細~
具體到這道題,引用一下zcysky在題解裏給出的解釋:

Splay可以用來維護序列。這樣的話是把Splay當作一棵區間樹。  
所謂區間樹和權值樹的區別,大概就是區間樹每個節點代表的是一段區間(典型代表就是一般的線段樹)  
權值樹好理解一點,就是每個點真的代表一個點。  
至於翻轉操作我們可以利用Splay的過程實現。詳見代碼。(Splay能維護序列反轉也是它作為LCT的輔助樹的條件之一)

作為模板題沒什麽好說的。這邊文章主要記錄板子用。感謝zcysky的板子。


代碼

#include<bits/stdc++.h>
#define N 100005
using namespace std;
int
n,m; int fa[N],ch[N][2],size[N],rev[N],rt;//fa[a]表示a的父親 inline void pushup(int x)//維護節點大小 { size[x]=size[ch[x][0]]+size[ch[x][1]]+1; } void pushdown(int x)//標記下傳 { if(rev[x]){//是否翻轉了區間 swap(ch[x][0],ch[x][1]); rev[ch[x][0]]^=1;rev[ch[x][1]]^=1;rev[x]=0; } } void rotate(int x,int &k)//旋轉 { int y=fa[x],z=fa[y],kind; if(ch[y][0]==x) kind=1; else kind=0; if(y==k) k=x; else { if(ch[z][0]==y) ch[z][0]=x; else ch[z][1]=x; } ch[y][kind^1]=ch[x][kind]; fa[ch[y][kind^1]]=y; ch[x][kind]=y; fa[y]=x; fa[x]=z; pushup(x); pushup(y); } void splay(int x,int &k)//伸展操作,將x一直旋轉直到x就是k { while(x!=k){ int y=fa[x],z=fa[y]; if(y!=k){ if((ch[y][0]==x)^(ch[z][0]==y)) rotate(x,k);//該節點與父親分別是他們爸的左孩子\右孩子或者是右孩子\左孩子旋轉2次x else rotate(y,k);//該節點與父親同是他們爸的左孩子或同是右孩子先旋轉一次y再旋轉一次x } rotate(x,k); } } void build(int l,int r,int f) //建立一顆完全平衡的二叉樹 { if(l>r) return; int mid=(l+r)/2; if(mid<f) ch[f][0]=mid; else ch[f][1]=mid; fa[mid]=f; size[mid]=1; if(l==r) return; build(l,mid-1,mid); build(mid+1,r,mid); pushup(mid); } int find(int x,int k)//尋找以x為根的子樹裏第k大的 { pushdown(x); int s=size[ch[x][0]]; if(k==s+1) return x; if(k<=s) return find(ch[x][0],k); else return find(ch[x][1],k-s-1); } void rever(int l,int r)//關於如何從Splay中提取區間請看上文思路中的論文 { int x=find(rt,l),y=find(rt,r+2); splay(x,rt); splay(y,ch[x][1]); int z=ch[y][0]; rev[z]^=1; } int main() { scanf("%d%d",&n,&m); rt=(n+3)/2; build(1,n+2,rt);//區間左右各多加1個數方便提取區間 for(int i=1;i<=m;i++){ int L,R; scanf("%d%d",&L,&R); rever(L,R); } for(int i=2;i<=n+1;i++) printf("%d ",find(rt,i)-1); return 0; }

洛谷:P3384 【模板】文藝平衡樹(Splay)