兩種簡單的平衡樹
前言
\(Q:\) 為什么是兩種
\(A:\) 因為我只看了 兩種 現在變成三種了~
前面丟的是關於二叉查找樹的一些性質和操作 (但我沒寫過也不會寫二叉查找樹) 來源於一本通提高篇 不想看略過即可
另外 剛開始學 如果發現有寫的不准確或者直接錯誤的地方勞煩指出
\(update\): 2021.3.17 補充 文藝平衡樹
\(update\): 2021.6.10 補充 \(FHQ-Treap\)
二叉查找樹 (\(BST\))
先補一下二分查找樹的相關
二叉查找樹的性質:
- 樹種每個節點被賦予了一個權值
- 若左子樹不空 則左子樹上所有節點的值均小於他的根節點的值
- 若右子樹不空 則左子樹上所有節點的值均大於他的根節點的值
- 其左右子樹也分別為二叉查找樹
二叉查找樹常用來維護有序的集合 有序的數集 建立索引或優先隊列等
遍歷
中序遍歷 左 -> 自身 -> 右
查找
- 從根節點開始查找
- 當前節點就是要查找的值 查找成功
- 查找的值小於當前節點 在左子樹中繼續查找
- 查找的值大於當前節點 在右子樹中繼續查找
- 當前節點為空 查找失敗 即該值在樹中不存在
查找最值
最小值:若左子節點不為空 持續訪問左子節點 直到為空 當前值即為要找的值
最大值:若右子節點不為空 持續訪問右子節點 直到為空 當前值即為要找的值
插入
- 從根節點開始插入
- 如果要插入的值小於當前節點的值 在當前節點的左子樹中插入
- 如果要插入的值大於當前節點的值 在當前節點的右子樹中插入
- 如果當前節點為空節點 在此建立新節點 該節點的值為要插入的值 左右子樹為空 插入成功
- 當插入的值與當前值相同時 在該節點上再加一個值 用於記錄這一值的數量
刪除
- 該節點為葉子節點 直接刪除
- 該節點為鏈節點 以其子節點代替自身 然后刪除
- 該節點有兩個非空子節點 以其后繼結點的右孩子代替其后繼結點 取其后繼結點代替當前節點 當前節點的左孩子置為后繼結點的左孩子
題目: P3369 【模板】普通平衡樹
\(Treap\)
關於 \(Treap\)
\(Treap\) 是在二叉查找樹的基礎上多維護了一個優先值 或者說是在維護二叉查找樹的性質的同時維護了堆的性質
從權值上來看 \(Treap\) 是一棵二叉查找樹 從優先級上來看 \(Treap\) 是一個堆
由於我們的優先級是隨機賦予的 所以可以防止涼心出題人通過構造數據使二叉查找樹退化成鏈的情況 以保證各種操作實現的時間復雜度(當然 如果你的隨機值依舊近乎退化成鏈 建議去買彩票)
\(Treap\) 的期望深度與操作的遞歸層數都是期望 \(O(logn)\) 的
\(Treap\) 的實現
\(Treap\) 中的絕大部分操作都可以類比二叉查找樹 畢竟沒有本質區別
下面以代碼解釋為主
- 變量的含義
#define ls(x) (t[x].l)
#define rs(x) (t[x].r)
struct node {int l, r, v, size, rnd, w;}t[B];
\(l\) 與 \(r\) 記錄該節點的左右孩子的編號
\(v\) 是該點的權值
\(w\) 是該點的數的個數
\(size\) 以該點為根的子樹的大小
\(rnd\) 優先級
- 旋轉
旋轉的目的在於維護堆序
由於沒找到圖 所以請聽我口胡
旋..干脆看代碼注釋 畫個圖理解一下吧...說不清楚
void rturn(int &p) {
int y = ls(p);//記錄該點的左孩子
ls(p) = rs(y);//以該點的左孩子的右孩子代替該點的左孩子
rs(y) = p;//以該點代替該點的左孩子的右孩子
t[y].size = t[p].size;//傳遞孩子數量
up_date(p);//更新
p = y;//以該點的左孩子代替該點
}
void lturn(int &p) {//同上
int y = rs(p);
rs(p) = ls(y);
ls(y) = p;
t[y].size = t[p].size;
up_date(p);
p = y;
}
算了 還是加個圖吧 感覺自己在扯淡
(圖是自己畫的 挺麻煩 還不好看)
圖示為將二號節點右旋的操作 可以對圖理解
- 插入操作
void insert(int &p, int x)
{
if(!p) {p = ++cnt; t[p].size = t[p].w = 1; t[p].v = x; t[p].rnd = rand(); return ;}//還沒有這個點 新建一個點 並初始化
t[p].size++; if(t[p].v == x) {t[p].w++; return ;}//對於插入路徑上的所有點都要維護 size 如果這個點已經有了 記錄這個數的次數
if(x > t[p].v) {insert(rs(p), x); if(t[rs(p)].rnd < t[p].rnd) lturn(p);}
else {insert(ls(p), x); if(t[ls(p)].rnd < t[p].rnd) rturn(p);}//比較 向左或向右 同時通過旋轉維護堆序
}
- 刪除
void del(int &p, int x)
{
if(!p) return ;//不存在
if(t[p].v == x)//找到了~
{
if(t[p].w > 1) {t[p].size--; t[p].w--; return ;}//如果這個數有多個 刪一個
if(!(ls(p) * rs(p))) p = ls(p) + rs(p);//只有一個 則如果是葉子節點或鏈節點 用孩子節點代替該點 相當於刪除(如果是葉子結點 子節點是空節點)
else if(t[ls(p)].rnd < t[rs(p)].rnd) rturn(p), del(p, x);//不是葉子或者是鏈的話 根據左右孩子的堆序 將該點不斷旋轉 直到成為葉子節點或鏈節點
else lturn(p), del(p, x);
}
else if(x > t[p].v) t[p].size--, del(rs(p), x);
else t[p].size--, del(ls(p), x);//判斷找的方向 一路維護 size的值 畢竟刪是一定要刪的 一路上的子樹大小都會有影響
}
- 查詢 \(x\) 的排行
int rank(int p, int x)
{
if(!p) return 0;//空
if(t[p].v == x) return t[ls(p)].size + 1;//找到了 左子樹里面的數一定都小於根節點 所以返回左子樹的大小加一
if(x > t[p].v) return t[ls(p)].size + t[p].w + rank(rs(p), x);//去右側找的時候要加上左子樹的大小和該點的數
return rank(ls(p), x);//左側則不用
}
- 查詢排行 \(x\) 的數
int num(int p, int x)
{
if(!p) return 0;//空
if(x <= t[ls(p)].size) return num(ls(p), x);//小於左子樹的大小 在左子樹
if(x > t[ls(p)].size + t[p].w) return num(rs(p), x - t[ls(p)].size - t[p].w);//大於左子樹加上該點的數的大小 在右邊
return t[p].v;//否則這個點就是
}
- 查前驅
void pre(int p, int x)
{
if(!p) return ;
if(t[p].v < x) _p = p, pre(rs(p), x);//找到一個比要查的數小的點 先記下來 在去右邊找
else pre(ls(p), x);//去左邊找
}
- 查后繼
void suc(int p, int x)//同上
{
if(!p) return ;
if(t[p].v > x) _p = p, suc(ls(p), x);
else suc(rs(p), x);
}
完整代碼:
(不要在吐槽縮進的問題了~)
/*
Time: 2.3
Worker: Blank_space
Source:
*/
/*--------------------------------------------*/
#include<cstdio>
#include<algorithm>
using namespace std;
/*--------------------------------------頭文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const int FFF = 0x8fffffff;
/*------------------------------------常量定義*/
int n, _p, p, cnt;
/*------------------------------------變量定義*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
/*----------------------------------------快讀*/
namespace Treap {
#define ls(x) (t[x].l)
#define rs(x) (t[x].r)
struct node {int l, r, v, size, rnd, w;}t[B];
void up_date(int p) {t[p].size = t[ls(p)].size + t[rs(p)].size + t[p].w;}
void rturn(int &p) {int y = ls(p); ls(p) = rs(y); rs(y) = p; t[y].size = t[p].size; up_date(p); p = y;}
void lturn(int &p) {int y = rs(p); rs(p) = ls(y); ls(y) = p; t[y].size = t[p].size; up_date(p); p = y;}
void insert(int &p, int x)
{
if(!p) {p = ++cnt; t[p].size = t[p].w = 1; t[p].v = x; t[p].rnd = rand(); return ;}
t[p].size++; if(t[p].v == x) {t[p].w++; return ;}
if(x > t[p].v) {insert(rs(p), x); if(t[rs(p)].rnd < t[p].rnd) lturn(p);}
else {insert(ls(p), x); if(t[ls(p)].rnd < t[p].rnd) rturn(p);}
}
void del(int &p, int x)
{
if(!p) return ;
if(t[p].v == x)
{
if(t[p].w > 1) {t[p].size--; t[p].w--; return ;}
if(!(ls(p) * rs(p))) p = ls(p) + rs(p);
else if(t[ls(p)].rnd < t[rs(p)].rnd) rturn(p), del(p, x);
else lturn(p), del(p, x);
}
else if(x > t[p].v) t[p].size--, del(rs(p), x);
else t[p].size--, del(ls(p), x);
}
int rank(int p, int x)
{
if(!p) return 0;
if(t[p].v == x) return t[ls(p)].size + 1;
if(x > t[p].v) return t[ls(p)].size + t[p].w + rank(rs(p), x);
return rank(ls(p), x);
}
int num(int p, int x)
{
if(!p) return 0;
if(x <= t[ls(p)].size) return num(ls(p), x);
if(x > t[ls(p)].size + t[p].w) return num(rs(p), x - t[ls(p)].size - t[p].w);
return t[p].v;
}
void pre(int p, int x)
{
if(!p) return ;
if(t[p].v < x) _p = p, pre(rs(p), x);
else pre(ls(p), x);
}
void suc(int p, int x)
{
if(!p) return ;
if(t[p].v > x) _p = p, suc(ls(p), x);
else suc(rs(p), x);
}
}
/*----------------------------------------函數*/
int main()
{
n = read();
for(int i = 1; i <= n; i++)
{
int opt = read(), x = read();
if(opt == 1) Treap::insert(p, x);
if(opt == 2) Treap::del(p, x);
if(opt == 3) printf("%d\n", Treap::rank(p, x));
if(opt == 4) printf("%d\n", Treap::num(p, x));
if(opt == 5) {_p = 0, Treap::pre(p, x); printf("%d\n", Treap::t[_p].v);}
if(opt == 6) {_p = 0, Treap::suc(p, x); printf("%d\n", Treap::t[_p].v);}
}
return 0;
}
\(Splay\)
關於 \(Splay\) —— 權值樹
同樣是平衡樹 其維護平衡的方式是不斷將需要的節點旋轉到根節點 來實現各種操作 碼量比 \(Treap\) 要大 而且理解起來稍微麻煩一點 常數也要比 \(Treap\) 大 但似乎更好用的亞子
\(Splay\)(權值樹) 的實現
- 變量的含義
#define fa(x) t[x].fa
#define ls(x) t[x].son[0]
#define rs(x) t[x].son[1]
int rt, cnt, st[C], top;//rt根節點 cnt根的編號 st刪去的節點 棧指針
struct node {int son[2], fa, w, v, siz;}t[C];//平衡樹主體 son 0左 1右孩子 fa父節點 w數的多少 v數的大小 siz以該點為根的子樹大小
- 一些小的操作
int fson(int p) {return p == rs(fa(p));}//判斷這個點是其父親的左孩子還是右孩子
void up_date(int p) {t[p].siz = t[ls(p)].siz + t[rs(p)].siz + t[p].w;}//更新該節點的孩子數
- 旋轉操作
最關鍵的兩個函數 也是最難解釋的兩個函數
由於圖實在是不想畫 所以就gu了
void rotate(int p) {//實現旋轉的函數
int fath = fa(p), x = fson(p);//找到該節點的父節點 以及該節點是父節點的左孩子還是右孩子
if(fa(fa(p))) t[fa(fa(p))].son[fson(fa(p))] = p;//如果這個點的父節點不是根節點 以該節點代替其父節點 更新其父節點與他父節點的父節點的關系
fa(p) = fa(fa(p)); t[fath].son[x] = t[p].son[x ^ 1];//更新該節點與其父節點的關系 實際上替換為了父節點的父節點 更新孩子位置
fa(t[fath].son[x]) = fath; t[p].son[x ^ 1] = fath; fa(fath) = p;//...寫不清楚...
up_date(fath); up_date(p);//更新該節點以及父節點的孩子個數
}
void splay(int p) {//節點的旋轉
for(; fa(p); rotate(p)) if(fa(fa(p))) rotate(fson(p) == fson(fa(p)) ? fa(p) : p);//將 p 點旋轉到根節點
rt = p;//rt相當於一個指向根節點的指針
}
- 加點操作
int add(int fath, int x) {//加點
int p = top ? st[top--] : ++cnt;//優先使用釋放的空間
fa(p) = fath; t[p].v = x; t[p].siz = t[p].w = 1;//新建節點初始化
return p;//返回新加點的編號
}
- 插入操作
void insert(int p, int fath, int x) {//插入
if(p && t[p].v != x) {insert(t[p].son[t[p].v < x], p, x); return ;}//判斷插入的位置(左/右)
if(t[p].v == x) t[p].w++;//找到 插入
if(!p) {p = add(fath, x); if(fa(p)) t[fa(p)].son[t[fa(p)].v < x] = p;}//沒有這個數 在其父親下面新建一個點 並確定其與父節點的關系(左/右)
up_date(p); up_date(fa(p)); splay(p);//更新孩子數量 並將新插入的點旋轉到根節點
}
- 刪除操作
void clear(int p) {fa(p) = rs(p) = ls(p) = t[p].v = t[p].w = t[p].siz = 0; st[++top] = p;}//刪除的節點釋放空間 備用
int find(int p, int x) {//找
if(!p) return 0;//該點不存在
if(x < t[p].v) return find(ls(p), x);
if(x == t[p].v) {splay(p); return 1;}//找到 並將這個點旋轉到根節點
return find(rs(p), x);
}
void del(int x) {//刪除
if(!find(rt, x)) return ;//該點是否存在 存在的話旋轉到根節點
if(t[rt].w > 1) {t[rt].w--; up_date(rt); return ;}//該點有多個數
int srt = rt;//記一下原來的根節點
if(!ls(rt) && !rs(rt)) rt = 0;//一個點 直接刪
else if(!ls(rt)) rt = rs(rt), fa(rt) = 0;
else if(!rs(rt)) rt = ls(rt), fa(rt) = 0;//只有一個孩子 把孩子提上來 刪
else if(ls(rt) && rs(rt))//兩個孩子
{
int lmax = ls(rt);//記左子樹的根節點
while(rs(lmax)) lmax = rs(lmax);//根據二叉查找樹的性質 找左邊最大的值
splay(lmax);//把左邊值最大的節點轉到根節點
rs(rt) = rs(srt); fa(rs(rt)) = rt;//把右子樹接過來
}
clear(srt); up_date(rt);//釋放空間 更新根節點的孩子數
}
- 查找 \(x\) 的排名
int rank(int x) {//找 x 的排名
insert(rt, 0, x);//把這個值插入 這個值被轉到根節點
int res = t[ls(rt)].siz + 1;//左子樹的點數 + 1 就是這個數的排名
del(x);//再刪掉
return res;
}
- 查找排名 \(x\) 的數
int num(int x) {//找排名 x 的數
int p = rt;//取根節點
while(1)//找
{
if(!p) return -1;//不存在 (沒數)
if(ls(p) && t[ls(p)].siz >= x) p = ls(p);//小於左子樹的節點數 在左邊
else//在右邊
{
x -= ls(p) ? t[ls(p)].siz : 0;//減去左邊子樹的節點數 到右邊找
if(x <= t[p].w) {splay(p); return t[p].v;}//小於這個點的數的數量 找到了 把這個點轉到根節點
x -= t[p].w; p = rs(p);//不在這里 繼續找
}
}
}
- 查找前驅
int pre(int x) {//查前驅
insert(rt, 0, x);//插入 這個點會轉到根節點
int p = ls(rt);//取左子樹的根節點
while(rs(p)) p = rs(p);//在左子樹中往右找 直到最后 也就是找到比根節點小的最大值
del(x);//把插入的點刪了
return t[p].v;
}
- 查找后繼
int suc(int x) {//查后繼 基本同上
insert(rt, 0, x);
int p = rs(rt);
while(ls(p)) p = ls(p);
del(x);
return t[p].v;
}
完整代碼:
/*
Time: 2.21
Worker: Blank_space
Source: P3369 【模板】普通平衡樹
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#define Max(x, y) ((x) > (y) ? (x) : (y))
#define Min(x, y) ((x) < (y) ? (x) : (y))
/*--------------------------------------頭文件*/
const int M = 1e4;
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const int FFF = 0x8fffffff;
/*------------------------------------常量定義*/
int n;
/*------------------------------------變量定義*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
/*----------------------------------------快讀*/
void _min(int &x, int y) {x = x < y ? x : y;}
void _max(int &x, int y) {x = x > y ? x : y;}
void _abs(int &x) {x = x < 0 ? -x : x;}
namespace Splay {
#define fa(x) t[x].fa
#define ls(x) t[x].son[0]
#define rs(x) t[x].son[1]
int rt, cnt, st[C], top;//根節點 根的編號 刪去的節點 棧指針
struct node {int son[2], fa, w, v, siz;}t[C];//平衡樹主體 son 0左 1右孩子 fa父節點 w數的多少 v數的大小 siz以該點為根的子樹大小
int fson(int p) {return p == rs(fa(p));}//判斷這個點是其父親的左孩子還是右孩子
void clear(int p) {fa(p) = rs(p) = ls(p) = t[p].v = t[p].w = t[p].siz = 0; st[++top] = p;}//刪除的節點釋放空間 備用
void up_date(int p) {t[p].siz = t[ls(p)].siz + t[rs(p)].siz + t[p].w;}//更新該節點的孩子數
int add(int fath, int x) {//加點
int p = top ? st[top--] : ++cnt;//優先使用釋放的空間
fa(p) = fath; t[p].v = x; t[p].siz = t[p].w = 1;//新建節點初始化
return p;//返回新加點的編號
}
void rotate(int p) {//實現旋轉的函數
int fath = fa(p), x = fson(p);//找到該節點的父節點 以及該節點是父節點的左孩子還是右孩子
if(fa(fa(p))) t[fa(fa(p))].son[fson(fa(p))] = p;//如果這個點的父節點不是根節點 以該節點代替其父節點 更新其父節點與他父節點的父節點的關系
fa(p) = fa(fa(p)); t[fath].son[x] = t[p].son[x ^ 1];//更新該節點與其父節點的關系 實際上替換為了父節點的父節點 更新孩子位置
fa(t[fath].son[x]) = fath; t[p].son[x ^ 1] = fath; fa(fath) = p;//...寫不清楚...
up_date(fath); up_date(p);//更新該節點以及父節點的孩子個數
}
void splay(int p) {//節點的旋轉
for(; fa(p); rotate(p)) if(fa(fa(p))) rotate(fson(p) == fson(fa(p)) ? fa(p) : p);//將 p 點旋轉到根節點
rt = p;//rt相當於一個指向根節點的指針
}
void insert(int p, int fath, int x) {//插入
if(p && t[p].v != x) {insert(t[p].son[t[p].v < x], p, x); return ;}//判斷插入的位置(左/右)
if(t[p].v == x) t[p].w++;//找到 插入
if(!p) {p = add(fath, x); if(fa(p)) t[fa(p)].son[t[fa(p)].v < x] = p;}//沒有這個數 在其父親下面新建一個點 並確定其與父節點的關系(左/右)
up_date(p); up_date(fa(p)); splay(p);//更新孩子數量 並將新插入的點旋轉到根節點
}
int find(int p, int x) {//找
if(!p) return 0;//該點不存在
if(x < t[p].v) return find(ls(p), x);
if(x == t[p].v) {splay(p); return 1;}//找到 並將這個點旋轉到根節點
return find(rs(p), x);
}
void del(int x) {//刪除
if(!find(rt, x)) return ;//該點是否存在 存在的話旋轉到根節點
if(t[rt].w > 1) {t[rt].w--; up_date(rt); return ;}//該點有多個數
int srt = rt;//記一下原來的根節點
if(!ls(rt) && !rs(rt)) rt = 0;//一個點 直接刪
else if(!ls(rt)) rt = rs(rt), fa(rt) = 0;
else if(!rs(rt)) rt = ls(rt), fa(rt) = 0;//只有一個孩子 把孩子提上來 刪
else if(ls(rt) && rs(rt))//兩個孩子
{
int lmax = ls(rt);//記左子樹的根節點
while(rs(lmax)) lmax = rs(lmax);//根據二叉查找樹的性質 找左邊最大的值
splay(lmax);//把左邊值最大的節點轉到根節點
rs(rt) = rs(srt); fa(rs(rt)) = rt;//把右子樹接過來
}
clear(srt); up_date(rt);//釋放空間 更新根節點的孩子數
}
int rank(int x) {//找 x 的排名
insert(rt, 0, x);//把這個值插入 這個值被轉到根節點
int res = t[ls(rt)].siz + 1;//左子樹的點數 + 1 就是這個數的排名
del(x);//再刪掉
return res;
}
int num(int x) {//找排名 x 的數
int p = rt;//取根節點
while(1)//找
{
if(!p) return -1;//不存在 (沒數)
if(ls(p) && t[ls(p)].siz >= x) p = ls(p);//小於左子樹的節點數 在左邊
else//在右邊
{
x -= ls(p) ? t[ls(p)].siz : 0;//減去左邊子樹的節點數 到右邊找
if(x <= t[p].w) {splay(p); return t[p].v;}//小於這個點的數的數量 找到了 把這個點轉到根節點
x -= t[p].w; p = rs(p);//不在這里 繼續找
}
}
}
int pre(int x) {//查前驅
insert(rt, 0, x);//插入 這個點會轉到根節點
int p = ls(rt);//取左子樹的根節點
while(rs(p)) p = rs(p);//在左子樹中往右找 直到最后 也就是找到比根節點小的最大值
del(x);//把插入的點刪了
return t[p].v;
}
int suc(int x) {//查后繼 基本同上
insert(rt, 0, x);
int p = rs(rt);
while(ls(p)) p = ls(p);
del(x);
return t[p].v;
}
}
/*----------------------------------------函數*/
int main() {
n = read();
while(n--)
{
int opt = read(), x = read();
if(opt == 1) Splay::insert(Splay::rt, 0, x);
if(opt == 2) Splay::del(x);
if(opt == 3) printf("%d\n", Splay::rank(x));
if(opt == 4) printf("%d\n", Splay::num(x));
if(opt == 5) printf("%d\n", Splay::pre(x));
if(opt == 6) printf("%d\n", Splay::suc(x));
}
return 0;
}
題目: P3391 【模板】文藝平衡樹
關於 \(Splay\) —— 區間樹
\(Splay\) 是可以用來維護序列的 這樣的 \(Splay\) 就是一棵區間樹(有點類似於線段樹的亞子) 大概是每個點 就是.. 一個點? 好像...就是一個點
值得注意的是 這里的 \(Splay\) 已經不是一棵二分查找樹了 所以並不一定滿足二分查找樹的性質 維護的時候對於每個點多維護一個 \(size\) 查找一個數的時候不在是按照權值去找(實際上權值在這里已經不是關鍵值了) 而是按照排名找
我們構建一棵完美的二叉樹 對於一個點 它在二叉樹中左子樹的孩子數加一就是其在原序列中的下標 而由於我們建成的是一棵完美的二叉樹 所以不管你怎么轉 這一點都是不變的
還需要知道的就是 \(Splay\) 中維護的關鍵值是原序列對應的下標的值 所以其中序遍歷就是原序列 我們進行區間 \([l, r]\) 翻轉的操作時 可以先將 \(l - 1\) 對應的節點旋轉到根節點 再將 \(r + 1\) 對應的節點旋轉到根節點的右子樹 這樣 這個右子樹的左子樹就是區間 \([l, r]\) 了 然后我們給這個點打上標記 每次查找時 如果有標記 就將左右子樹交換 標記下傳就可以了(類似線段樹)
\(Splay\)(區間樹) 代碼實現
關於注釋的話 就先 gu 了 后面再補上
/*
Time: 3.17
Worker: Blank_space
Source: P3391 【模板】文藝平衡樹
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#include<algorithm>
#define Max(x, y) ((x) > (y) ? (x) : (y))
#define Min(x, y) ((x) < (y) ? (x) : (y))
#define Abs(x) ((x) < 0 ? -(x) : (x))
/*--------------------------------------頭文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定義*/
int n, m, cnt, rt;
/*------------------------------------變量定義*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
/*----------------------------------------快讀*/
namespace Splay {
#define fa(x) t[x].fa
#define ls(x) t[x].son[0]
#define rs(x) t[x].son[1]
struct node {int fa, son[2], v, siz, w; bool lzy;}t[C];
bool fson(int p) {return rs(fa(p)) == p;}
void push_up(int p) {t[p].siz = t[ls(p)].siz + t[rs(p)].siz + t[p].w;}
void push_down(int p) {
if(!p || !t[p].lzy) return ;
t[ls(p)].lzy ^= 1; t[rs(p)].lzy ^= 1; std::swap(ls(p), rs(p));
t[p].lzy = 0;
}
int add(int fath, int x) {
cnt++; t[cnt].w = t[cnt].siz = 1; t[cnt].fa = fath; t[cnt].v = x;
if(x == 0 || x == n + 1) t[cnt].v = x ? -INF : INF;
return cnt;
}
void rotate(int p) {
int fath = fa(p), gfa = fa(fath), x = fson(p);
t[fath].son[x] = t[p].son[x ^ 1]; fa(t[fath].son[x]) = fath;
t[p].son[x ^ 1] = fath; fa(fath) = p; fa(p) = gfa;
if(gfa) t[gfa].son[t[gfa].son[1] == fath] = p;
push_up(fath); push_up(p);
}
void splay(int p, int tar) {
for(int fath = fa(p); (fath = fa(p)) != tar; rotate(p))
if(fa(fath) != tar) rotate(fson(p) == fson(fath) ? fath : p);
if(!tar) rt = p;
}
int find(int p, int x) {
while(1)
{
push_down(p);
if(x <= t[ls(p)].siz) {p = ls(p); continue;}
if(x == t[ls(p)].siz + 1) return p;
x -= t[ls(p)].siz + 1; p = rs(p);
}
}
void reverse(int l, int r) {
l = find(rt, l); r = find(rt, r); splay(l, 0); splay(r, l);
t[ls(rs(rt))].lzy ^= 1;
}
int build(int fath, int l, int r) {
if(l > r) return 0;
int mid = (l + r) >> 1, p = add(fath, mid);
ls(p) = build(p, l, mid - 1); rs(p) = build(p, mid + 1, r);
push_up(p); return p;
}
void print(int p) {
if(!p) return ; push_down(p);
print(ls(p));
if(t[p].v >= 1 && t[p].v <= n) printf("%d ", t[p].v);
print(rs(p));
}
}
/*----------------------------------------函數*/
int main() {
n = read(); m = read(); rt = Splay::build(0, 0, n + 1);
for(int i = 1; i <= m; i++)
{
int l = read(), r = read();
Splay::reverse(l, r + 2);
}
Splay::print(rt);
return 0;
}
\(FHQ-Treap\)
關於 \(FHQ-Treap\)
這是我最喜歡的一種數據結構 沒有之一
\(Treap\) 的顯著特點就是在 \(BST\) 上多維護了一個關鍵值 無旋 \(Treap\) 也一樣 只不過這棵平衡樹保持平衡的方法是通過分裂與合並來實現的 這是一種功能非常強大的數據結構 其實你甚至可以拿這個東西當線段樹用 復雜度是期望 \(O(n\log n)\) 的
分裂與合並是一種非常好的操作 這種操作方式天生的支持維護序列和持久化之類的操作 完全可以比肩 \(Splay\)
代碼簡短 區間操作強悍 支持可持久化 也可以用來寫 \(LCT\) 單點修改容易掛(\(SBT\) 比較好一點 可能?)
其實真正學完以后發現這和 \(Treap\) 好像根本不是一個東西
直接上操作了
\(FHQ-Treap\) 的實現
- 變量含義
struct node {int son[2], siz, val, rnd;} t[B];
int cnt;
\(son\) 左右兒子
\(siz\) 以該點為根的子樹大小
\(val\) 該點權值
\(rnd\) 優先值
- 最關鍵的操作之一 分裂
\(FHQ\) 的分裂我知道的有兩種 一種是按照權值分裂 另一種是按照子樹大小分裂 根據不同的題目自由選擇
- 按照權值大小分裂
一般將權值小於等於給定值的節點分裂到左樹 大於給定值的節點分裂到右樹
分裂過程:
- 每到一個點時 比較當前點的權值與給定權值的大小
- 若小於等於給定權值 將該點及所有左子樹的節點歸入左樹 然后遞歸分裂右子樹
- 若大於給定權值 將該點及所有右子樹的節點歸入右樹 然后遞歸分裂左子樹
- 葉子節點 不再分裂 退出
代碼實現:
void split(int p, int val, int &x, int &y) {
if(!p) {x = y = 0; return ;}
if(t[p].val <= val) x = p, split(rs(p), val, rs(p), y);
else y = p, split(ls(p), val, x, ls(p)); up_date(p);
}
- 按照子樹大小分裂
將子節點數量小於等於給定值的節點分裂到左樹 大於給定值的節點分裂到右樹
分裂過程:
- 到達一個點時 比較當前點的左兒子的 \(siz\) 與給定值的大小
- 若小於等於給定值 將該點及所有左子樹的節點歸入左樹 然后遞歸分裂右子樹
- 若大於給定值 將該點及所有右子樹的節點歸入右樹 **將給定值減去左子樹的 \(siz + 1\) ** 然后遞歸分裂右子樹
- 葉子結點 不再分裂 退出
代碼實現:
void split(int p, int k, int &x, int &y) {
if(!p) {x = y = 0; return ;}
if(k <= t[ls(p)].siz) y = p, split(ls(p), k, x, ls(p));
else x = p, split(rs(p), k - t[ls(p)].siz - 1, rs(p), y); push_up(p);
}
\(ps\) :
一開始學的時候寫的代碼向上合並的函數名稱是 \(up\_date\) 后來有了新的認識 函數名改為了 \(push\_up\)
另外按照子樹分裂的這段代碼並不是這道模板題的 而是另一道
單次分裂的復雜度是 \(O(\log n)\)
- 最關鍵的操作之二 合並
合並兩棵根節點不同的樹 返回新根節點的編號
一般都是分裂完之后合並 或者是新建節點並合並到某棵子樹上
合並的時候只考慮兩根的優先級 也就是維護堆序
合並過程:
- 若左樹優先級小於右樹優先級 左樹為根 將右樹與左樹的右子樹進行遞歸合並
- 否則 右樹為根 將左樹與右樹的左子樹進行遞歸合並
- 當其中任意一個子樹全部合並時 返回另一個 退出
代碼實現:
int merge(int x, int y) {
if(!x || !y) return x + y;
if(t[x].rnd < t[y].rnd) {rs(x) = merge(rs(x), y); up_date(x); return x;}
ls(y) = merge(x, ls(y)); up_date(y); return y;
}
之后的其他所有操作都是在這兩個操作的基礎上實現的 所以請務必好好理解分裂與合並
- 新建節點
根據給定權值新建一個節點進行初始化並返回節點編號
代碼實現:
int add(int val) {
int p = ++Tcnt; t[p].siz = 1; t[p].val = val; t[p].rnd = rand();
return p;
}
- 插入
新建一個節點 並合並入現有的樹中
按照權值進行分裂 新建一個節點 與左樹合並 再將右樹合並上去
代碼實現:
void ins(int val) {split(rt, val, tmp1, tmp2); rt = merge(merge(tmp1, add(val)), tmp2);}
- 刪除
刪除指定權值的節點
將指定權值的點分裂出來 合並時排除這個點 具體看代碼
void del(int val) {
split(rt, val, tmp1, tmp3); split(tmp1, val - 1, tmp1, tmp2);
tmp2 = merge(ls(tmp2), rs(tmp2)); rt = merge(merge(tmp1, tmp2), tmp3);
}
- 查詢給定權值的排名
按給定值 \(-1\) 進行分裂 左子樹大小 \(+1\) 就是排名
void query_rank(int val) {split(rt, val - 1, tmp1, tmp2); printf("%d\n", t[tmp1].siz + 1); rt = merge(tmp1, tmp2);}
- 查詢給定排名的權值
這個和普通的 \(Treap\) 是一樣的 略過了
int Kth(int p, int rank) {
while(1)
{
if(rank <= t[ls(p)].siz) {p = ls(p); continue;}
if(rank == t[ls(p)].siz + 1) return p;
rank -= t[ls(p)].siz + 1; p = rs(p);
}
}
- 查詢前驅后繼
前驅:
按照給定權值 \(-1\) 分裂 左樹中最大值即為前驅
后繼:
按照給定權值分裂 右樹中最小值即為后繼
代碼
void query_pre(int val) {split(rt, val - 1, tmp1, tmp2); printf("%d\n", t[Kth(tmp1, t[tmp1].siz)].val); rt = merge(tmp1, tmp2);}
void query_suc(int val) {split(rt, val, tmp1, tmp2); printf("%d\n", t[Kth(tmp2, 1)].val); rt = merge(tmp1, tmp2);}
完整代碼
/*
Time: 6.6
Worker: Blank_space
Source: P3369 【模板】普通平衡樹
*/
/*--------------------------------------------*/
#include<cstdio>
#include<algorithm>
/*--------------------------------------頭文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定義*/
inline void File() {
freopen(".in", "r", stdin);
freopen(".out", "w", stdout);
}
/*----------------------------------------文件*/
int n, rt;
/*------------------------------------變量定義*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
/*----------------------------------------快讀*/
namespace FHQ_Treap {
#define ls(x) t[x].son[0]
#define rs(x) t[x].son[1]
struct node {int son[2], val, siz, rnd;} t[B];
int tmp1, tmp2, tmp3, Tcnt;
void up_date(int p) {t[p].siz = t[ls(p)].siz + t[rs(p)].siz + 1;}
int add(int val) {
int p = ++Tcnt; t[p].siz = 1; t[p].val = val; t[p].rnd = rand();
return p;
}
int merge(int x, int y) {
if(!x || !y) return x + y;
if(t[x].rnd < t[y].rnd) {rs(x) = merge(rs(x), y); up_date(x); return x;}
ls(y) = merge(x, ls(y)); up_date(y); return y;
}
void split(int p, int val, int &x, int &y) {
if(!p) {x = y = 0; return ;}
if(t[p].val <= val) x = p, split(rs(p), val, rs(p), y);
else y = p, split(ls(p), val, x, ls(p)); up_date(p);
}
void ins(int val) {split(rt, val, tmp1, tmp2); rt = merge(merge(tmp1, add(val)), tmp2);}
void del(int val) {
split(rt, val, tmp1, tmp3); split(tmp1, val - 1, tmp1, tmp2);
tmp2 = merge(ls(tmp2), rs(tmp2)); rt = merge(merge(tmp1, tmp2), tmp3);
}
int Kth(int p, int rank) {
while(1)
{
if(rank <= t[ls(p)].siz) {p = ls(p); continue;}
if(rank == t[ls(p)].siz + 1) return p;
rank -= t[ls(p)].siz + 1; p = rs(p);
}
}
void query_rank(int val) {split(rt, val - 1, tmp1, tmp2); printf("%d\n", t[tmp1].siz + 1); rt = merge(tmp1, tmp2);}
void query_pre(int val) {split(rt, val - 1, tmp1, tmp2); printf("%d\n", t[Kth(tmp1, t[tmp1].siz)].val); rt = merge(tmp1, tmp2);}
void query_suc(int val) {split(rt, val, tmp1, tmp2); printf("%d\n", t[Kth(tmp2, 1)].val); rt = merge(tmp1, tmp2);}
}
using namespace FHQ_Treap;
/*----------------------------------------函數*/
int main() {
srand(mod); n = read();
for(int i = 1; i <= n; i++)
{
int opt = read(), x = read();
if(opt == 1) ins(x);
if(opt == 2) del(x);
if(opt == 3) query_rank(x);
if(opt == 4) printf("%d\n", t[Kth(rt, x)].val);
if(opt == 5) query_pre(x);
if(opt == 6) query_suc(x);
}
return 0;
}
例題
題解什么的沒時間寫了 先這樣吧
代碼
/*
Time: 6.8
Worker: Blank_space
Source: P3391 【模板】文藝平衡樹
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#include<algorithm>
#define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
/*--------------------------------------頭文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定義*/
inline void File() {
freopen(".in", "r", stdin);
freopen(".out", "w", stdout);
}
/*----------------------------------------文件*/
int n, m, rt, tmp1, tmp2, tmp3;
/*------------------------------------變量定義*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
/*----------------------------------------快讀*/
namespace FHQ {
#define ls(x) t[x].son[0]
#define rs(x) t[x].son[1]
#define mid (l + r >> 1)
struct node {int son[2], siz, val, lzy, rnd;} t[B]; int cnt;
void push_up(int p) {t[p].siz = t[ls(p)].siz + t[rs(p)].siz + 1;}
void f(int p) {Swap(ls(p), rs(p)); t[p].lzy ^= 1;}
void push_down(int p) {if(ls(p)) f(ls(p)); if(rs(p)) f(rs(p)); t[p].lzy = 0;}
void split(int p, int k, int &x, int &y) {
if(!p) {x = y = 0; return ;} if(t[p].lzy) push_down(p);
if(k <= t[ls(p)].siz) y = p, split(ls(p), k, x, ls(p));
else x = p, split(rs(p), k - t[ls(p)].siz - 1, rs(p), y); push_up(p);
}
int merge(int x, int y) {
if(!x || !y) return x + y;
if(t[x].rnd < t[y].rnd) {if(t[x].lzy) push_down(x); rs(x) = merge(rs(x), y); push_up(x); return x;}
else {if(t[y].lzy) push_down(y); ls(y) = merge(x, ls(y)); push_up(y); return y;}
}
int add(int val) {int p = ++cnt; t[p].val = val; t[p].siz = 1; t[p].rnd = rand(); return p;}
int build(int l, int r) {
if(l == r) return add(l);
int x = build(l, mid), y = build(mid + 1, r);
return merge(x, y);
}
void print(int p) {
if(!p) return ; if(t[p].lzy) push_down(p);
if(ls(p)) print(ls(p));
printf("%d ", t[p].val);
if(rs(p)) print(rs(p));
}
void reverse(int l, int r) {split(rt, r, tmp1, tmp3); split(tmp1, l - 1, tmp1, tmp2); f(tmp2); rt = merge(merge(tmp1, tmp2), tmp3);}
}
/*----------------------------------------函數*/
int main() {
n = read(); m = read(); rt = FHQ::build(1, n);
for(int i = 1; i <= m; i++)
{
int x = read(), y = read();
FHQ::reverse(x, y);
}
FHQ::print(rt);
return 0;
}
代碼
/*
Time: 6.8
Worker: Blank_space
Source: P3372 【模板】線段樹 1
區間加 區間求和
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#include<algorithm>
#define int long long
/*--------------------------------------頭文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
/*------------------------------------常量定義*/
inline void File() {
freopen(".in", "r", stdin);
freopen(".out", "w", stdout);
}
/*----------------------------------------文件*/
int n, m, rt, tmp1, tmp2, tmp3;
/*------------------------------------變量定義*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
/*----------------------------------------快讀*/
namespace FHQ {
#define ls(x) t[x].son[0]
#define rs(x) t[x].son[1]
#define mid (l + r >> 1)
struct node {int son[2], siz, val, sum, lzy, rnd;} t[B]; int cnt;
void push_up(int p) {
t[p].sum = t[ls(p)].sum + t[rs(p)].sum + t[p].val;
t[p].siz = t[ls(p)].siz + t[rs(p)].siz + 1;
}
void f(int p, int k) {t[p].sum += t[p].siz * k; t[p].val += k; t[p].lzy += k;}
void push_down(int p) {if(ls(p)) f(ls(p), t[p].lzy); if(rs(p)) f(rs(p), t[p].lzy); t[p].lzy = 0;}
void split(int p, int k, int &x, int &y) {
if(!p) {x = y = 0; return ;} push_down(p);
if(k <= t[ls(p)].siz) y = p, split(ls(p), k, x, ls(p));
else x = p, split(rs(p), k - t[ls(p)].siz - 1, rs(p), y); push_up(p);
}
int merge(int x, int y) {
if(!x || !y) return x + y;
if(t[x].rnd < t[y].rnd) {push_down(x); rs(x) = merge(rs(x), y); push_up(x); return x;}
else {push_down(y); ls(y) = merge(x, ls(y)); push_up(y); return y;}
}
int add(int val) {int p = ++cnt; t[p].val = t[p].sum = val; t[p].siz = 1; t[p].rnd = rand(); return p;}
int build(int l, int r) {
if(l == r) return add(read());
int x = build(l, mid), y = build(mid + 1, r);
return merge(x, y);
}
void up_date(int l, int r, int k) {
split(rt, r, tmp1, tmp3); split(tmp1, l - 1, tmp1, tmp2);
f(tmp2, k); rt = merge(merge(tmp1, tmp2), tmp3);
}
int query(int l, int r) {
split(rt, r, tmp1, tmp3); split(tmp1, l - 1, tmp1, tmp2);
int res = t[tmp2].sum; rt = merge(merge(tmp1, tmp2), tmp3); return res;
}
void work1() {int x = read(), y = read(), z = read(); up_date(x, y, z);}
void work2() {int x = read(), y = read(); printf("%lld\n", query(x, y));}
}
/*----------------------------------------函數*/
signed main() {
n = read(); m = read(); rt = FHQ::build(1, n);
for(int i = 1; i <= m; i++)
{
int opt = read();
if(opt == 1) FHQ::work1();
else FHQ::work2();
}
return 0;
}
/*
Time: 6.8
Worker: Blank_space
Source: P2710 數列
區間插入 區間刪除 區間翻轉 區間賦值 區間求和 查排名 區間最大字段和
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#include<algorithm>
#define int long long
#define Max(x, y) ((x) > (y) ? (x) : (y))
#define Swap(x, y) ((x) ^= (y) ^= (x) ^= (y))
/*--------------------------------------頭文件*/
const int B = 5e5 + 7;
const int mod = 1e9 + 7;
const int INF = 1e14;
/*------------------------------------常量定義*/
inline void File() {
freopen("P2710_8.in","r",stdin);
freopen("out","w",stdout);
}
/*----------------------------------------文件*/
int n, m, rt, tmp1, tmp2, tmp3;
char op[20];
/*------------------------------------變量定義*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
/*----------------------------------------快讀*/
namespace FHQ {
#define ls(x) t[x].son[0]
#define rs(x) t[x].son[1]
#define mid (l + r >> 1)
struct node {int son[2], lmax, rmax, max, sum, val, lzy1, lzy2, siz, rnd;} t[B];
int cnt, st[B], top;//1 覆蓋 2 翻轉
void push_up(int p) {
t[p].siz = t[ls(p)].siz + t[rs(p)].siz + 1;
t[p].sum = t[ls(p)].sum + t[rs(p)].sum + t[p].val;
t[p].lmax = Max(t[ls(p)].lmax, t[ls(p)].sum + t[p].val + t[rs(p)].lmax);
t[p].rmax = Max(t[rs(p)].rmax, t[rs(p)].sum + t[p].val + t[ls(p)].rmax);
t[p].max = Max(t[p].val, t[ls(p)].rmax + t[p].val + t[rs(p)].lmax);
if(ls(p)) t[p].max = Max(t[p].max, t[ls(p)].max);
if(rs(p)) t[p].max = Max(t[p].max, t[rs(p)].max);
}
void f1(int p, int k) {
t[p].sum = t[p].siz * k; t[p].lmax = t[p].rmax = Max(t[p].sum, 0);
t[p].max = Max(t[p].sum, k); t[p].lzy1 = t[p].val = k;
}
void f2(int p) {Swap(ls(p), rs(p)); Swap(t[p].lmax, t[p].rmax); t[p].lzy2 ^= 1;}
void push_down(int p) {
if(t[p].lzy1 != INF)
{
if(ls(p)) f1(ls(p), t[p].lzy1);
if(rs(p)) f1(rs(p), t[p].lzy1); t[p].lzy1 = INF;
}
if(t[p].lzy2) {if(ls(p)) f2(ls(p)); if(rs(p)) f2(rs(p)); t[p].lzy2 ^= 1;}
}
void split(int p, int k, int &x, int &y) {
if(!p) {x = y = 0; return ;} push_down(p);
if(k <= t[ls(p)].siz) y = p, split(ls(p), k, x, ls(p));
else x = p, split(rs(p), k - t[ls(p)].siz - 1, rs(p), y); push_up(p);
}
int merge(int x, int y) {
if(!x || !y) return x + y;
if(t[x].rnd < t[y].rnd) {push_down(x); rs(x) = merge(rs(x), y); push_up(x); return x;}
else {push_down(y); ls(y) = merge(x, ls(y)); push_up(y); return y;}
}
int add(int val) {
int p = top ? st[top--] : ++cnt; t[p].siz = 1; t[p].val = t[p].sum = t[p].max = val; ls(p) = rs(p) = 0;
t[p].lzy2 = 0; t[p].rnd = rand(); t[p].lmax = t[p].rmax = Max(val, 0); t[p].lzy1 = INF; return p;
}
void up_date(int l, int r, int k, bool opt) {
split(rt, r, tmp1, tmp3); split(tmp1, l - 1, tmp1, tmp2);
if(opt) f1(tmp2, k); else f2(tmp2); rt = merge(merge(tmp1, tmp2), tmp3);
}
int ins(int l, int r) {
if(l == r) return add(read());
int x = ins(l, mid), y = ins(mid + 1, r);
return merge(x, y);
}
void dele(int p) {if(ls(p)) dele(ls(p)); if(rs(p)) dele(rs(p)); st[++top] = p;}
void del(int l, int r) {
split(rt, r, tmp1, tmp3); split(tmp1, l - 1, tmp1, tmp2);
dele(tmp2); rt = merge(tmp1, tmp3);
}
node query(int l, int r) {
split(rt, r, tmp1, tmp3); split(tmp1, l - 1, tmp1, tmp2);
node res = t[tmp2]; rt = merge(merge(tmp1, tmp2), tmp3); return res;
}
void Insert() {
int x = read(), y = read(); split(rt, x, tmp1, tmp2);
rt = merge(merge(tmp1, ins(x, x + y - 1)), tmp2);
}
void Delete() {int x = read(), y = read(); del(x, x + y - 1);}
void Reveres() {int x = read(), y = read(); up_date(x, x + y - 1, 0, 0);}
void Make_Same() {int x = read(), y = read(), z = read(); up_date(x, x + y - 1, z, 1);}
void Get_Sum() {int x = read(), y = read(); printf("%lld\n", query(x, x + y - 1).sum);}
void Max_Sum() {int x = read(), y = read(); printf("%lld\n", query(x, x + y - 1).max);}
void Get() {int x = read(); printf("%lld\n", query(x, x).sum);}
}
/*----------------------------------------函數*/
signed main() {
n = read(); m = read(); rt = FHQ::ins(1, n);
for(int i = 1; i <= m; i++)
{
scanf("%s", op + 1); int len = strlen(op + 1);
if(op[1] == 'I') FHQ::Insert();
else if(op[1] == 'D') FHQ::Delete();
else if(op[1] == 'R') FHQ::Reveres();
else if(op[3] == 'K') FHQ::Make_Same();
else if(op[3] == 'X') FHQ::Max_Sum();
else if(len > 4) FHQ::Get_Sum();
else FHQ::Get();
}
return 0;
}
后記:
關於代碼的問題
\(Treap\) 的代碼主要借鑒的是 \(loceaner\) 的博客
\(Splay\) 的代碼主要借鑒的是 \(Luckyblock\) 的博客
所以寫法基本相差不是很大 把鏈接掛在后面