洛谷:P3384 【模板】文藝平衡樹(Splay)
阿新 • • 發佈:2018-02-23
定位 描述 ever 論文 這樣的 紅黑樹 裏的 來源 分別是
對於AVL樹是一種為了防止樹結構不夠優導致深度過深時間復雜度退化,在保持二叉搜索樹性質不變的前提下進行的一種變換。簡單說就是把往一邊沈的樹弄的兩邊平衡些。
而在Splay中,將特定點旋轉到一定位置可以進行提取區間等操作,同時各種旋轉間接的使樹基本平衡(是的,可以構造數據卡掉。Treap樹對此表示同情)。
下面兩幅圖應該有助於理解:
左旋(下面代碼裏的表達:把S往上轉一次)→
右旋(下面代碼裏的表達:把E往上轉一次)→
圖片來源:http://blog.csdn.net/sun_tttt/article/details/65445754
(文章是介紹紅黑樹的但是這個左旋右旋操作二叉搜索樹通用)
論文裏講的很詳細~
具體到這道題,引用一下zcysky在題解裏給出的解釋:
原題地址:https://www.luogu.org/problemnew/show/P3391
題目簡述
您需要寫一種數據結構(可參考題目標題),來維護一個有序數列,其中需要提供以下操作:
翻轉一個區間,例如原有序序列是5 4 3 2 1,翻轉區間是[2,4]的話,結果是5 2 3 4 1
思路
首先明白Splay比起線段樹能多幹什麽:
- 可以在一個有序序列中任意數後面動態插入一串數(不能比a後面一個數還大)
- 可以刪除一段區間
可能描述不是很清楚,具體看這裏面給的論文鏈接:信息學競賽相關優秀文章合集
或者直接看這裏:運用伸展樹解決數列維護問題.pdf
如果搞不懂左旋右旋是什麽,可以先看信息學競賽相關優秀文章合集裏的AVL樹介紹。
而在Splay中,將特定點旋轉到一定位置可以進行提取區間等操作,同時各種旋轉間接的使樹基本平衡(是的,可以構造數據卡掉。Treap樹對此表示同情)。
下面兩幅圖應該有助於理解:
左旋(下面代碼裏的表達:把S往上轉一次)→
右旋(下面代碼裏的表達:把E往上轉一次)→
圖片來源:http://blog.csdn.net/sun_tttt/article/details/65445754
(文章是介紹紅黑樹的但是這個左旋右旋操作二叉搜索樹通用)
論文裏講的很詳細~
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)