一種二叉樹的樹形資料結構,其定義如下:
一種自平衡二叉搜尋樹,通過不斷將某個節點旋轉到根節點,使得整棵樹仍然滿足二叉查詢樹的性質,且保持平衡而不至於退化為鏈
\(root\)
\(tot\)
\(fa[i]\)
\(child[i][0/1]\)
\(val[i]\)
\(cnt[i]\)
\(size[i]\)
根節點節點總數
父親左右兒子
點權出現次數
子樹大小
為調換\(splay\)中父子節點的位置,我們使用旋轉操作,將乙個節點向上移動乙個位置,並保證:void clear(int x)
void update(int x)
int get(int x)
\(splay\)中的旋轉分為兩種,左旋和右旋,這裡以右旋為例:
旋轉分為四個步驟\(:\)
(假設需要旋轉的節點為\(x\),其父親為\(y\))
\(1.\)將\(y\)的左兒子指向\(x\)的右兒子,且\(x\)的右兒子的父親指向\(y\)
\(2.\)將\(x\)的右兒子指向\(y\),且\(y\)的父親指向\(x\)child[y][0] = child[x][1];
fa[child[x][1]] = y;
\(3.\)如果\(y\)還有父親\(z\),將\(y\)原來在\(z\)中所在的位置指向\(x\),且\(x\)的父親指向\(y\)child[x][1] = y;
fa[y] = x;
\(4.\)更新\(x\)和\(y\)的\(size\)fa[x] = z;
child[z][y==child[z][1]] = x;
void rotate(int x)
\(splay\)規定,每訪問乙個節點後,都要強制將該節點旋轉到根的位置
為保證不退化成鏈,\(splay\)操作一共分三步
\(1.\)如果父節點為目標位置,則向上旋轉
\(2.\)如果當前節點與父節點的「關係」和父節點與祖父節點的「關係」相同,則先旋轉父節點,再旋**身
\(3.\)如果不滿足以上條件,則將自身連續旋轉兩次
重複以上操作,直到旋轉到根
設插入的值為\(k\)void splay(int x,int goal)
if (!goal) root=x;
}
if(!root)
同時需要將該節點\(splay\)到根的位置
乙個數的前驅定義為小於\(x\)的最大的數int now = root,f = 0;
while(1)
f = now;
now = child[now][val[now]設\(x\)為需要查詢的值
根據二叉搜尋樹的性質:
int rank(int k)
else
now = child[now][1];}}
}
後繼定義為大於\(x\)的最小的數
顯然,乙個數的前驅是其左子樹中最靠右的節點,後繼是其右子樹的最靠左的節點
將\(x\)旋轉到根後查詢即可
首先將要刪除的節點\(x\)旋轉到根的位置int pre()
int next()
否則
void del(int k)
if(!child[root][0]&&!child[root][1])
if(!child[root][0])
if(!child[root][1])
int now = root,x = pre();
fa[child[now][1]] = x;
child[x][1] = child[now][1];
clear(now);
update(root);
}
給乙個長度為\(n\)的序列,序列中第\(a_i\)項的初始值為\(i\)#includeusing namespace std;
const int maxn = 100005;
int child[maxn][2],size[maxn],root,tot,fa[maxn],val[maxn],cnt[maxn];
#define ci(q) scanf("%d",&q)
struct splay
bool pd(int x)
void clear(int x)
void rotate(int x)
void splay(int x)
root = x;
} void insert(int k)
int now = root,f = 0;
while(1)
f = now;
now = child[now][val[now]1)
if(!child[root][0]&&!child[root][1])
if(!child[root][0])
if(!child[root][1])
int now = root,x = pre();
fa[child[now][1]] = x;
child[x][1] = child[now][1];
clear(now);
update(root);
}}tree;
int main()
return 0;
}
有\(m\)次區間翻轉操作,輸出經過 \(m\) 次變換後的結果
按點的編號建立一顆\(splay\)
每次翻轉時
先將\(val = l-1\)和\(val = r+1\)的節點分別轉到根和根的兒子節點
根據\(splay\)的性質,整顆樹的中序遍歷不變
因此只需要將\(child[child[root][1]][0]\)下的所有子樹交換
通過給打懶標記的方式來實現交換操作即可
splay-oi-wiki#includeusing namespace std;
const int maxn = 100005;
const int inf = 114514191;
int child[maxn][2],a[maxn],size[maxn],root,tot,fa[maxn],val[maxn],cnt[maxn],tag[maxn];
struct splay
void update(int x)
bool pd(int x)
void clear(int x)
void rotate(int x)
int build(int l,int r,int f)
void splay(int x,int goal)
if (!goal) root=x;
} int kth(int k)
else
k-=t;
now = child[now][1];
}} }
void reverse(int x,int y)
void dfs(int x)
tree.dfs(root);
}
splay 學習筆記(一)-menci
splay 學習筆記
核心函式splay 每次訪問乙個節點,都把該節點轉到根 包括插入 查詢 操作 思想 對於訪問頻率較高的節點,使其處於根節點附近,從而保證log複雜度 splay可以維護中序遍歷是有序序列 也可維護 中序遍歷是當前操作後的序列 const int n 1e5 10 struct nodet n int...
Splay學習筆記
n n 500000 個數,要求維護區間加,區間查詢 很簡單,用線段樹 樹狀陣列隨便寫寫就能過 n n 500000 個數,維護插入 刪除 找區間第k大 小 用平衡樹 set也能過 n n 500000 個數,維護區間翻轉,區間查詢,插入,刪除 這個時候就需要用到伸展樹 splay splay是一種...
學習筆記 splay入門(更新中)
宣告 本部落格所有隨筆都參照了網路資料或其他部落格,僅為博主想加深理解而寫,如有疑問歡迎與博主討論 終於學習了 spaly splay 聽說了很久,因為dalao總是那這個開玩笑所以對它有深深的恐懼.但是學起來沒有那麼難啦,可能是因為提前學了替罪羊樹?學替罪羊樹真的是痛苦555 p3369 模板 普...