BST
又稱二分查詢樹,\(BST\) 性質指其左子樹所有節點全職均小於該點,其右子樹所有節點全職均大於該點;同時若對該棵樹進行中序遍歷,所產生的序列為從小到大排序的序列。
利用該性質,從而在 \(O(\log(n))\) 的複雜度內實現查詢排名、第 \(k\) 小(大)值、前驅、後繼等。
當每次插入的資料呈單調性時,其內部將形成一條鏈,從而使複雜度退化為 \(O(n)\) 。
Treap
當資料隨機時,其內部時趨於平衡的,\(Treap\) 就是基於隨機化與二叉堆使其實現平衡。
對於如下的幾種基本操作單次複雜度均為 \(O(\log(n))\) 。
旋轉
對於內一個點賦予其一個隨機值,使這棵樹是該隨機值的二叉堆。
具體如何旋轉操作在 \(splay\) 那裡會有更詳細的解釋。
旋轉操作是使 \(Treap\) 達到平衡的核心。
旋轉操作
void rotate(int &p,bool d)
{
int son=f.ch[d];
f.ch[d]=t[son].ch[d^1];
t[son].ch[d^1]=p;
pushup(p);
pushup(p=son);
}
插入
找到其應插入的位置,若書中已存在該值則 \(cnt++\) ,否則建立新點,同時根據其隨機值旋轉,使達到平衡。
插入操作
void insert(int &p,int x)
{
if(p==0)
{
p=++tot;
f.cnt=f.size=1;
f.val=x;
f.dat=rand();
return ;
}
f.size++;
if(f.val==x)
{
f.cnt++;
return ;
}
bool d=f.val<x;
insert(f.ch[d],x);
if(f.dat>t[f.ch[d]].dat) rotate(p,d);
}
刪除
找到所要刪除點所在位置,如果其 \(cnt>1\) ,直接 \(cnt--\) 即可。
否則,如果該節點只有一個子節點,那麼直接讓子節點替換其位置即可;若該點為葉子節點,那麼直接刪掉它不會產生任何其他的影響。
因為 \(Treap\) 支援旋轉操作,將其直接旋轉到滿足上面兩種情況即可。
刪除操作
void remove(int &p,int x)
{
if(p==0) return ;
if(f.val==x)
{
if(f.cnt>1)
{
f.cnt--;
f.size--;
return ;
}
bool d=ls.dat>rs.dat;
if(f.l==0||f.r==0) p=f.l+f.r;
else rotate(p,d),remove(p,x);
}
else f.size--,remove(f.ch[f.val<x],x);
}
查詢排名
\(x\) 的排名定義為比 \(x\) 小的個數 \(+1\) 。
因為其滿足 \(BST\) 性質,從根節點開始向下尋找,設當前點權值為 \(val\) ,\(ls、rs\) 分別指左右兒子,\(cnt\) 指該節點重複插入次數,\(size\) 指對應節點子樹的大小。
-
\(val==x\) ,即已經找到答案,返回 \(ls.size+1\) 。
-
\(val>x\) ,繼續向其左兒子方向尋找。
-
\(val<x\) ,繼續向右兒子方向尋找,同時答案加上 \(ls.size+cnt\) 。
在很多時候要查詢的數並不在樹裡,但同時根據比他小的個數加一仍可以求出其排名,常見解決辦法有兩種,不會對複雜度產生較大影響。
-
在查詢前先插入該點,查詢後再將該點刪除。
-
當尋找到一個不存在的點時,顯然只有所查詢數不在樹中時才可能出現這種情況,此時令他返回 \(1\) 而不是 \(0\) ,即比他小的個數 \(+1\) 。
查詢排名
int ask_rk(int p,int x)
{
if(p==0) return 1;
if(f.val==x) return ls.size+1;
if(f.val>x) return ask_rk(f.l,x);
return ask_rk(f.r,x)+ls.size+f.cnt;
}
查詢第 \(k\) 小(大)值
以查詢第 \(k\) 小值為例,因為其滿足 \(BST\) 性質,設 \(ls、rs\) 分別指左右兒子,\(size\) 指對應節點子樹的大小,\(cnt\) 指該節點重複插入次數。
-
\(ls.size>=k\) ,繼續向其左兒子方向尋找。
-
\(ls.size<k\&\&ls.size+cnt>=k\) ,該節點即為答案。
-
\(ls.size+cnt<k\) ,另 \(k\) 減去 \(ls.size+cnt\) ,繼續向其右兒子方向尋找。
查詢第 k 小值
int ask_val(int p,int x)
{
if(p==0) return inf;
if(ls.size>=x) return ask_val(f.l,x);
if(ls.size+f.cnt>=x) return f.val;
return ask_val(f.r,x-ls.size-f.cnt);
}
查詢前驅、後繼
以前驅為例。
根據 \(BST\) 性質,找到第一個小於其的位置,之後不停向右兒子方向尋找。
查詢前驅
int pre(int p,int x)
{
if(p==0) return -inf;
if(f.val>=x) return pre(f.l,x);
return max(pre(f.r,x),f.val);
}
查詢後繼
int nxt(int p,int x)
{
if(p==0) return inf;
if(f.val<=x) return nxt(f.r,x);
return min(nxt(f.l,x),f.val);
}
fhq_Treap
又名無旋 \(Treap\) ,以分裂與合併為核心操作代替旋轉使其實現平衡。
其分裂與合併分為按照權值合併與按照樹的大小合併兩種,當維護序列時按照大小分則更加方便,此處以按照權值分為例,另一種將在文藝平衡樹中講解。
分裂
將 \(p\) 分為 \(x、y\) 兩部分,\(x\) 中節點全部 \(\leq k\) ,\(y\) 中節點全部 \(>k\) 。
-
\(val>k\) ,說明其左子樹全部在 \(x\) 內,故 \(y=p\) ,向左兒子方向繼續分裂。
-
反之,其右子樹全部在 \(y\) 內,則 \(x=p\) ,繼續向右兒子方向分裂。
分裂操作
void split(int p,int k,int &x,int &y)
{
if(!p) {x=y=0; return ;}
if(f.val>k) y=p,split(f.l,k,x,t[y].l);
else x=p,split(f.r,k,t[x].r,y);
pushup(p);
}
合併
通常是將被分開的兩部分再合併起來,將 \(x、y\) 合併成 \(p\) ,滿足 \(x\) 中節點全部小於 \(y\) 中節點。
此時就可以按照其隨機值合併,使其平衡。
合併操作
int merge(int x,int y)
{
if(!x||!y) return x+y;
int p;
if(t[x].dat>t[y].dat)t[x].r=merge(t[x].r,y),pushup(p=x);
else t[y].l=merge(x,t[y].l),pushup(p=y);
return p;
}
插入
將 \(k\) 插入。
將 \(root\) 按照 \(k-1\) 分裂為 \(x、y\) ,再按照 \(x、new、y\) 合併起來。
插入操作
void insert(int &p,int k)
{
t[++tot].val=k,t[tot].size=1,t[tot].dat=rand();
int x,y;
split(p,k-1,x,y);
p=merge(merge(x,tot),y);
}
刪除
將 \(k\) 刪除。
依舊將 \(root\) 按照 \(k-1\) 分裂成 \(x、y\) ,再將 \(y\) 按照 \(k\) 分裂成 \(is、y\) ,將其按照 \(x、is.ls,is.rs,y\) 合併。
刪除操作
void remove(int &p,int k)
{
int x,y,is;
split(p,k-1,x,y),split(y,k,is,y);
p=merge(merge(x,merge(t[is].l,t[is].r)),y);
}
查詢排名
查 \(k\) 的排名。
將 \(root\) 按照 \(k-1\) 分裂為 \(x、y\) ,\(x.size+1\) 即為所求。
查詢排名
int ask_rk(int p,int k)
{
int x,y,is;
split(p,k-1,x,y);
is=t[x].size+1;
p=merge(x,y);
return is;
}
其餘操作與 \(Treap\) 基本一致。
優點
-
為所有平衡樹中最好打,最容易理解的一種。
-
因為不需要旋轉,所以可以可持久化。
-
允許有權值一樣的不同節點,因為其是按照權值分裂的。
-
因為按照權值分裂,所以不在樹中的數也可以直接查。
-
當其按照樹的大小分裂時,可以實現維護序列,這是 \(Treap\) 做不到的,大多數 \(splay\) 能做的他都能做。
splay
\(splay\) 的核心在於其雙旋操作,使其不依賴於隨機值。
在每次操作後,將該點進行雙旋轉到根節點的位置,從而使複雜度達到均攤單次 \(O(\log(n))\) ,其複雜度分析詳見 oi-wiki 。
哨兵節點
即新增 \(-inf、inf\) 節點,為極小值和極大值,使其避免查詢出界,會對一些操作產生需要 \(±1\) 的操作。
單旋操作
設 \(x\) 是 \(y\) 在 \(k\) 方向上的兒子.
旋轉後 \(y\) 是 \(x\) 在 \(k\bigoplus 1\) 方向上的兒子。
\(x\) 在 \(k\bigoplus 1\) 方向上的兒子成為 \(y\) 在 \(k\) 方向上的兒子。
其餘不變。
單旋操作
void rotate(int x)
{
int y=t[x].ff,z=t[y].ff,k=fson(y,x);
t[z].ch[fson(z,y)]=x;
t[x].ff=z;
t[y].ch[k]=t[x].ch[k^1];
t[t[x].ch[k^1]].ff=y;
t[x].ch[k^1]=y;
t[y].ff=x;
pushup(y),pushup(x);
}
splay 操作(雙旋操作)
將點 \(x\) 旋轉到 \(goal\) 的子節點位置。
定義 \(fa_x=y,fa_y=z\) ,當 \(x、y、z\) 滿足在同一條鏈時,先旋轉 \(y\) ,再旋轉 \(x\) ,使其滿足平衡。
如圖:
將 \(x\) 不停進行單旋操作,左圖中最大深度為 \(4\) ,右圖最大深度仍為 \(4\) ,隨意其實沒有意義的。
對於上面所說的情況,如果先旋轉 \(y\) ,再旋轉 \(x\) ,最後為:
從而達到平衡。
splay 操作
void splay(int x,int goal)
{
while(t[x].ff!=goal)
{
int y=t[x].ff,z=t[y].ff;
if(z!=goal)
fson(z,y)^fson(y,x)?rotate(x):rotate(y);
rotate(x);
}
if(goal==0) root=x;
}
另外為了方便,有一個 \(find\) 操作,即找到某點位置,再將其轉到根節點。
find 操作
void find(int x)
{
int p=root;
if(!p) return ;
while(f.ch[x>f.val]&&x!=f.val) p=f.ch[x>f.val];
splay(p,0);
}
插入操作
找到離 \(x\) 最近的點,若該點權值就是 \(x\) ,則 \(cnt++\) ,否則建一個新的點,最後都要將其旋轉到根節點。
插入操作
void insert(int x)
{
int p=root,ff=0;
while(p&&f.val!=x)
ff=p,
p=f.ch[x>f.val];
if(p) {f.cnt++; splay(p,0); return ;}
p=++tot;
if(ff) t[ff].ch[x>t[ff].val]=p;
f.ff=ff,f.val=x,f.size=f.cnt=1;
splay(p,0);
}
刪除操作
刪除操作需要先找到其前驅與後繼對應的位置,將前驅轉到根節點,再將後繼轉到前驅的右兒子位置,那麼此時他們倆中間夾著的,即後繼的左兒子,即為 \(x\) 的位置,刪除即可。
刪除操作
void remove(int x)
{
int pre=pre_nxt(x,0),nxt=pre_nxt(x,1);
splay(pre,0),splay(nxt,pre);
int p=t[nxt].l;
if(f.cnt>1) f.cnt--,splay(p,0);
else t[nxt].l=0;
}
查詢前驅、後繼
以前驅為例。
先找到離 \(x\) 最近的位置,如果該點權值就是 \(x\) ,先到其左兒子,然後向右不斷地找,即為所求。
如果該點值不是 \(x\) ,若其恰好滿足比 \(x\) 小,則該點即為所求,否則和上述一樣即可。
注意此處找到的是其前驅的位置,不是數值,為了方便刪除操作。
查詢前驅、後繼
int pre_nxt(int x,bool d)
{
find(x);
int p=root;
if((f.val>x&&d)||(f.val<x&&!d)) return p;
p=f.ch[d];
while(f.ch[d^1]) p=f.ch[d^1];
return p;
}
查詢排名
此時需要滿足 \(x\) 一定在樹內,所以可以先插入,待查詢後再刪除。
先找到 \(x\) 所在位置,將其轉到根節點位置,則此時其左兒子個數 \(+1\) 即為所求。
由於新增了哨兵節點,實際還需要 \(-1\) ,所以 \(ls.size\) 即為所求。
查詢排名
int ask_rk(int x)
{
find(x);
int p=root;
return ls.size;
}
查詢第 \(k\) 小(大)值
與 \(Treap\) 一致。
優點
因為其 \(splay\) 操作,所以其能夠進行維護序列等各種操作,完成所有 \(fhq\) 能支援甚至不支援的各種操作,但是常熟更大,碼量更長,不如 \(fhq\) 好理解。
例題
基本操作
前三道為最基本的查詢前驅後繼、第 \(k\) 大值等問題。
luogu P2234 營業額統計
luogu P2286 寵物收養場
luogu P1486 鬱悶的出納員
luogu P3369 普通平衡樹
Treap
#include<bits/stdc++.h>
// #define int long long
#define endl '\n'
#define sort stable_sort
#define f t[p]
#define ls t[t[p].ch[0]]
#define rs t[t[p].ch[1]]
#define l ch[0]
#define r ch[1]
using namespace std;
const int N=1e5+10,inf=0x7f7f7f7f;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int n,tot,root;
struct treap
{
int ch[2],cnt,size,val,dat;
}t[N];
void pushup(int p)
{
f.size=ls.size+rs.size+f.cnt;
}
void rotate(int &p,bool d)
{
int son=f.ch[d];
f.ch[d]=t[son].ch[d^1];
t[son].ch[d^1]=p;
pushup(p);
pushup(p=son);
}
void insert(int &p,int x)
{
if(p==0)
{
p=++tot;
f.cnt=f.size=1;
f.val=x;
f.dat=rand();
return ;
}
f.size++;
if(f.val==x)
{
f.cnt++;
return ;
}
bool d=f.val<x;
insert(f.ch[d],x);
if(f.dat>t[f.ch[d]].dat) rotate(p,d);
}
void remove(int &p,int x)
{
if(p==0) return ;
if(f.val==x)
{
if(f.cnt>1)
{
f.cnt--;
f.size--;
return ;
}
bool d=ls.dat>rs.dat;
if(f.l==0||f.r==0) p=f.l+f.r;
else rotate(p,d),remove(p,x);
}
else f.size--,remove(f.ch[f.val<x],x);
}
int ask_rk(int p,int x)
{
if(p==0) return 1;
if(f.val==x) return ls.size+1;
if(f.val>x) return ask_rk(f.l,x);
return ask_rk(f.r,x)+ls.size+f.cnt;
}
int ask_val(int p,int x)
{
if(p==0) return inf;
if(ls.size>=x) return ask_val(f.l,x);
if(ls.size+f.cnt>=x) return f.val;
return ask_val(f.r,x-ls.size-f.cnt);
}
int pre(int p,int x)
{
if(p==0) return -inf;
if(f.val>=x) return pre(f.l,x);
return max(pre(f.r,x),f.val);
}
int nxt(int p,int x)
{
if(p==0) return inf;
if(f.val<=x) return nxt(f.r,x);
return min(nxt(f.l,x),f.val);
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
read(n);
while(n--)
{
int op,x;
read(op),read(x);
switch(op)
{
case 1: insert(root,x); break;
case 2: remove(root,x); break;
case 3: write(ask_rk(root,x)),puts(""); break;
case 4: write(ask_val(root,x)),puts(""); break;
case 5: write(pre(root,x)),puts(""); break;
case 6: write(nxt(root,x)),puts(""); break;
}
}
}
fhq_Treap
#include<bits/stdc++.h>
// #define int long long
#define endl '\n'
#define sort stable_sort
#define f t[p]
#define ls t[t[p].ch[0]]
#define rs t[t[p].ch[1]]
#define l ch[0]
#define r ch[1]
using namespace std;
const int N=1e5+10,inf=0x7f7f7f7f;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int n,root,tot;
struct fhq_treap
{
int ch[2],val,dat,size;
}t[N];
void pushup(int p) {f.size=ls.size+rs.size+1;}
void split(int p,int k,int &x,int &y)
{
if(!p) {x=y=0; return ;}
if(f.val>k) y=p,split(f.l,k,x,t[y].l);
else x=p,split(f.r,k,t[x].r,y);
pushup(p);
}
int merge(int x,int y)
{
if(!x||!y) return x+y;
int p;
if(t[x].dat>t[y].dat)t[x].r=merge(t[x].r,y),pushup(p=x);
else t[y].l=merge(x,t[y].l),pushup(p=y);
return p;
}
void insert(int &p,int k)
{
t[++tot].val=k,t[tot].size=1,t[tot].dat=rand();
int x,y;
split(p,k-1,x,y);
p=merge(merge(x,tot),y);
}
void remove(int &p,int k)
{
int x,y,is;
split(p,k-1,x,y),split(y,k,is,y);
p=merge(merge(x,merge(t[is].l,t[is].r)),y);
}
int ask_rk(int p,int k)
{
int x,y,is;
split(p,k-1,x,y);
is=t[x].size+1;
p=merge(x,y);
return is;
}
int ask_val(int p,int k)
{
if(!p) return inf;
if(ls.size>=k) return ask_val(f.l,k);
if(ls.size+1>=k) return f.val;
return ask_val(f.r,k-ls.size-1);
}
int pre(int p,int k)
{
if(!p) return -inf;
if(f.val>=k) return pre(f.l,k);
return max(pre(f.r,k),f.val);
}
int nxt(int p,int k)
{
if(!p) return inf;
if(f.val<=k) return nxt(f.r,k);
return min(nxt(f.l,k),f.val);
}
signed main()
{
read(n);
for(int i=1,op,x;i<=n;i++)
{
read(op),read(x);
switch(op)
{
case 1: insert(root,x); break;
case 2: remove(root,x); break;
case 3: write(ask_rk(root,x)),puts(""); break;
case 4: write(ask_val(root,x)),puts(""); break;
case 5: write(pre(root,x)),puts(""); break;
case 6: write(nxt(root,x)),puts(""); break;
}
}
}
splay
#include<bits/stdc++.h>
// #define int long long
#define endl '\n'
#define sort stable_sort
#define f t[p]
#define ls t[t[p].ch[0]]
#define rs t[t[p].ch[1]]
#define l ch[0]
#define r ch[1]
using namespace std;
const int N=1e5+10,inf=0x7f7f7f7f;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int n,root,tot;
struct splay
{
int ch[2],ff,size,cnt,val;
}t[N];
bool fson(int x,int y) {return (t[x].ch[1]==y);}
void pushup(int p) {f.size=ls.size+rs.size+f.cnt;}
void rotate(int x)
{
int y=t[x].ff,z=t[y].ff,k=fson(y,x);
t[z].ch[fson(z,y)]=x;
t[x].ff=z;
t[y].ch[k]=t[x].ch[k^1];
t[t[x].ch[k^1]].ff=y;
t[x].ch[k^1]=y;
t[y].ff=x;
pushup(y),pushup(x);
}
void splay(int x,int goal)
{
while(t[x].ff!=goal)
{
int y=t[x].ff,z=t[y].ff;
if(z!=goal)
fson(z,y)^fson(y,x)?rotate(x):rotate(y);
rotate(x);
}
if(goal==0) root=x;
}
void find(int x)
{
int p=root;
if(!p) return ;
while(f.ch[x>f.val]&&x!=f.val) p=f.ch[x>f.val];
splay(p,0);
}
void insert(int x)
{
int p=root,ff=0;
while(p&&f.val!=x)
ff=p,
p=f.ch[x>f.val];
if(p) {f.cnt++; splay(p,0); return ;}
p=++tot;
if(ff) t[ff].ch[x>t[ff].val]=p;
f.ff=ff,f.val=x,f.size=f.cnt=1;
splay(p,0);
}
int pre_nxt(int x,bool d)
{
find(x);
int p=root;
if((f.val>x&&d)||(f.val<x&&!d)) return p;
p=f.ch[d];
while(f.ch[d^1]) p=f.ch[d^1];
return p;
}
void remove(int x)
{
int pre=pre_nxt(x,0),nxt=pre_nxt(x,1);
splay(pre,0),splay(nxt,pre);
int p=t[nxt].l;
if(f.cnt>1) f.cnt--,splay(p,0);
else t[nxt].l=0;
}
int ask_rk(int x)
{
find(x);
int p=root;
return ls.size;
}
int ask_val(int p,int x)
{
if(!p) return inf;
if(ls.size>=x) return ask_val(f.l,x);
if(ls.size+f.cnt>=x) return f.val;
return ask_val(f.r,x-ls.size-f.cnt);
}
signed main()
{
insert(-inf),insert(inf);
read(n);
for(int i=1,op,x;i<=n;i++)
{
read(op),read(x);
switch(op)
{
case 1: insert(x); break;
case 2: remove(x); break;
case 3:
insert(x);
write(ask_rk(x)),puts("");
remove(x);
break;
case 4: write(ask_val(root,x+1)),puts(""); break;
case 5: write(t[pre_nxt(x,0)].val),puts(""); break;
case 6: write(t[pre_nxt(x,1)].val),puts(""); break;
}
}
}
維護序列操作
luogu P3391 文藝平衡樹
利用平衡樹實現維護序列操作,可以使用 \(fhq\) 或 \(splay\) 。
-
\(fhq\)
對於每次旋轉,將其按照樹的大小分裂,先按照 \(r\) 分裂, 再將左半部分按照 \(l-1\) 分裂,則中間一部分即為所求區間,向下打標記即可。
翻轉即左右兒子互換。
最後中序遍歷輸出。
文藝平衡樹(fhq)
#include<bits/stdc++.h> // #define int long long #define endl '\n' #define sort stable_sort #define f t[p] #define ls t[t[p].ch[0]] #define rs t[t[p].ch[1]] #define l ch[0] #define r ch[1] using namespace std; const int N=1e5+10,inf=0x7f7f7f7f; template<typename Tp> inline void read(Tp&x) { x=0;register bool z=true; register char c=getchar(); for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0; for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48); x=(z?x:~x+1); } void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');} void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);} int n,m,tot,root; struct fhq_treap { int ch[2],size,dat,val; bool add; }t[N]; void pushup(int p) {f.size=ls.size+rs.size+1;} void spread(int p) { if(!f.add||!p) return ; swap(f.l,f.r); ls.add^=1,rs.add^=1; f.add=0; } void split(int p,int k,int &x,int &y) { if(!p) {x=y=0; return ;} spread(p); if(ls.size>=k) y=p,split(f.l,k,x,t[y].l); else x=p,split(f.r,k-ls.size-1,t[x].r,y); pushup(p); } int merge(int x,int y) { if(!x||!y) return x+y; spread(x),spread(y); int p; if(t[x].dat>t[y].dat) t[x].r=merge(t[x].r,y),pushup(p=x); else t[y].l=merge(x,t[y].l),pushup(p=y); return p; } void insert(int &p,int k) { t[++tot].val=k,t[tot].size=1,t[tot].dat=rand(); p=merge(p,tot); } void solve(int &p,int ll,int rr) { int x,y,is; split(p,rr,x,y),split(x,ll-1,x,is); t[is].add^=1; p=merge(merge(x,is),y); } void dfs(int p) { if(!p) return ; spread(p); dfs(f.l); write(f.val),putchar(' '); dfs(f.r); } signed main() { read(n),read(m); for(int i=1;i<=n;i++) insert(root,i); for(int i=1,ll,rr;i<=m;i++) read(ll),read(rr), solve(root,ll,rr); dfs(root); }
-
\(splay\)
找到 \(l-1\) 的位置,定義為 \(x\) ,\(r+1\) 的位置,定義為 \(y\) ,將 \(x\) 轉到根節點,再將 \(y\) 轉為 \(x\) 的子節點,那麼根節點的右兒子的左兒子即對應所求區間,其餘操作與 \(fhq\) 一致。
文藝平衡樹(splay)
#include<bits/stdc++.h> // #define int long long #define endl '\n' #define sort stable_sort #define f t[p] #define ls t[t[p].ch[0]] #define rs t[t[p].ch[1]] #define l ch[0] #define r ch[1] using namespace std; const int N=1e5+10,inf=0x7f7f7f7f; template<typename Tp> inline void read(Tp&x) { x=0;register bool z=true; register char c=getchar(); for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0; for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48); x=(z?x:~x+1); } void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');} void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);} int n,m,root,tot; struct splay { int ch[2],ff,size,cnt,val; bool add; }t[N]; bool fson(int x,int y) {return t[x].ch[1]==y;} void pushup(int p) {f.size=ls.size+rs.size+f.cnt;} void spread(int p) { if(!p||!f.add) return ; swap(f.l,f.r); ls.add^=1,rs.add^=1; f.add=0; } void rotate(int x) { int y=t[x].ff,z=t[y].ff,k=fson(y,x); t[z].ch[fson(z,y)]=x; t[x].ff=z; t[y].ch[k]=t[x].ch[k^1]; t[t[x].ch[k^1]].ff=y; t[x].ch[k^1]=y; t[y].ff=x; pushup(y),pushup(x); } void splay(int x,int goal) { while(t[x].ff!=goal) { int y=t[x].ff,z=t[y].ff; if(z!=goal) fson(z,y)^fson(y,x)?rotate(x):rotate(y); rotate(x); } if(goal==0) root=x; } void insert(int x) { int p=root,ff=0; while(p&&f.val!=x) ff=p, p=f.ch[x>f.val]; if(p) {f.cnt++; splay(p,0); return ;} p=++tot; if(ff) t[ff].ch[x>t[ff].val]=p; f.ff=ff,f.val=x,f.size=f.cnt=1; splay(p,0); } int find(int p,int x) { spread(p); if(ls.size>=x) return find(f.l,x); if(ls.size+f.cnt>=x) return p; return find(f.r,x-ls.size-f.cnt); } void solve(int x,int y) { x=find(root,x),y=find(root,y); splay(x,0),splay(y,x); t[t[t[root].r].l].add^=1; } void dfs(int p) { if(!p) return ; spread(p); dfs(f.l); if(f.val!=1&&f.val!=n+2) write(f.val-1),putchar(' '); dfs(f.r); } signed main() { read(n),read(m); for(int i=1;i<=n+2;i++) insert(i); for(int i=1,ll,rr;i<=m;i++) read(ll),read(rr), solve(ll,rr+2); dfs(root); }
luogu P3165 排序機械臂
因為其不會插入新點,所以可以透過排序得知第 \(k\) 小值的編號。
對於查詢其所在位置,利用 \(splay\) 將其轉到根節點,則根節點的左兒子的 \(size+1\) 即為所求。
區間翻轉操作與文藝平衡樹類似,同時向樹中存的應為下標而不是權值。
注意審題與哨兵節點插入的順序。
機械臂排序
#include<bits/stdc++.h>
// #define int long long
#define endl '\n'
#define sort stable_sort
#define f t[p]
#define ls t[t[p].ch[0]]
#define rs t[t[p].ch[1]]
#define l ch[0]
#define r ch[1]
using namespace std;
const int N=1e5+10,inf=0x7f7f7f7f;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int n,root,tot,a[N];
struct aa {int h,id;}e[N];
bool cmp(aa a,aa b) {return a.h==b.h?a.id<b.id:a.h<b.h;}
struct splay
{
int ch[2],ff,size,cnt,val;
bool add;
}t[N];
bool fson(int x,int y) {return t[x].ch[1]==y;}
void pushup(int p) {f.size=ls.size+rs.size+f.cnt;}
void spread(int p)
{
if(!p||!f.add) return ;
swap(f.l,f.r);
ls.add^=1,rs.add^=1;
f.add=0;
}
void rotate(int x)
{
int y=t[x].ff,z=t[y].ff,k=fson(y,x);
t[z].ch[fson(z,y)]=x;
t[x].ff=z;
t[y].ch[k]=t[x].ch[k^1];
t[t[x].ch[k^1]].ff=y;
t[x].ch[k^1]=y;
t[y].ff=x;
pushup(y),pushup(x);
}
void splay(int x,int goal)
{
while(t[x].ff!=goal)
{
int y=t[x].ff,z=t[y].ff;
spread(z),spread(y),spread(x);
if(z!=goal)
fson(z,y)^fson(y,x)?rotate(x):rotate(y);
rotate(x);
}
if(goal==0) root=x;
}
void insert(int x)
{
int p=root,ff=0;
while(p&&f.val!=x)
ff=p,
p=f.ch[x>f.val];
if(p) {f.cnt++; splay(p,0); return ;}
p=++tot;
if(ff) t[ff].ch[x>t[ff].val]=p;
f.ff=ff,f.val=x,f.size=f.cnt=1;
splay(p,0);
}
int find(int p,int x)
{
spread(p);
if(ls.size>=x) return find(f.l,x);
if(ls.size+f.cnt>=x) return p;
return find(f.r,x-ls.size-f.cnt);
}
void solve(int x,int y)
{
x--,y++;
x=find(root,x),y=find(root,y);
splay(x,0),splay(y,x);
t[t[t[root].r].l].add^=1;
}
int ask(int x)
{
splay(x,0);
return t[t[root].l].size+1;
}
void solve(int i)
{
int x=i,y=ask(e[i].id);
solve(x,y);
}
signed main()
{
srand(time(0));
insert(1);
read(n);
for(int i=2;i<=n+1;i++)
read(e[i].h),
e[i].id=i,
insert(i);
insert(n+2);
sort(e+2,e+2+n,cmp);
for(int i=2;i<=n+1;i++)
write(ask(e[i].id)-1),putchar(' '),
solve(i);
}
luogu P4309 最長上升子序列
回想最長上升子序列的 \(DP\) 式子,\(dp_i=\max\limits_{0<j<i\&\&a_j<a_i}\{dp_j\}+1\) 。
此處由於每次插入的數一定大於之前插入的所有數,所以直接在 \(0<j<i\) 中找到最大的 \(dp_j+1\) 即為 \(dp_i\) 。
因為其需要動態插入,可以離線後線段樹維護,也可以平衡樹動態維護。
對於沒一個節點定義 \(val\) 表示其子樹中最大的 \(dp\) 值,\(dp\) 表示其自身的 \(dp\) 值,由此將子樹的資訊傳遞給根節點以便查詢。
void pushup(int p)
{
f.val=max({ls.val,rs.val,f.dp});
}
插入操作可以用 \(fhq\) 或 \(splay\) 支援。
最長上升子序列
#include<bits/stdc++.h>
// #define int long long
#define endl '\n'
#define sort stable_sort
#define f t[p]
#define ls t[t[p].ch[0]]
#define rs t[t[p].ch[1]]
#define l ch[0]
#define r ch[1]
using namespace std;
const int N=1e5+10,inf=0x7f7f7f7f;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int n,root,tot;
struct fhq_treap
{
int ch[2],size,dat,ans,len;
}t[N];
void pushup(int p)
{
f.size=ls.size+rs.size+1;
f.ans=max({ls.ans,rs.ans,f.len});
}
void split(int p,int k,int &x,int &y)
{
if(!p) {x=y=0; return ;}
if(ls.size>=k) y=p,split(f.l,k,x,t[y].l);
else x=p,split(f.r,k-ls.size-1,t[x].r,y);
pushup(p);
}
int merge(int x,int y)
{
if(!x||!y) return x+y;
int p;
if(t[x].dat>t[y].dat) t[x].r=merge(t[x].r,y),pushup(p=x);
else t[y].l=merge(x,t[y].l),pushup(p=y);
return p;
}
void insert(int &p,int k)
{
t[++tot].size=1,t[tot].dat=rand();
int x,y;
split(p,k,x,y);
t[tot].len=t[tot].ans=t[x].ans+1;
p=merge(merge(x,tot),y);
}
signed main()
{
srand(time(0));
read(n);
for(int i=1,x;i<=n;i++)
read(x),
insert(root,x),
write(t[root].ans),puts("");
}
樹上雜湊
對於每個節點,儲存的是其子樹的 \(hash\) 值。
void pushup(int p)
{
f.hash=ls.hash*b[rs.size+1]+f.val*b[rs.size]+rs.hash;
}
luogu P4036 火星人
利用樹上雜湊,對於每次詢問,二分答案即可。
查詢一個區間的 \(hash\) 詢問,與文藝平衡樹類似的,找到這個區間,此時的 \(hash\) 值即為所求。
火星人
#include<bits/stdc++.h>
// #define int long long
#define ull unsigned long long
#define endl '\n'
#define sort stable_sort
#define f t[p]
#define ls t[t[p].ch[0]]
#define rs t[t[p].ch[1]]
#define l ch[0]
#define r ch[1]
using namespace std;
const int N=3e5+10,inf=0x7f7f7f7f;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int n,m,tot,root;
ull b[N];
char s[N];
struct fhq_treap
{
int ch[2],size,dat;
ull val,hash;
}t[N];
void pushup(int p)
{
f.size=ls.size+rs.size+1;
f.hash=ls.hash*b[rs.size+1]+f.val*b[rs.size]+rs.hash;
}
void split(int p,int k,int &x,int &y)
{
if(!p) {x=y=0; return ;}
if(ls.size>=k) y=p,split(f.l,k,x,t[y].l);
else x=p,split(f.r,k-ls.size-1,t[x].r,y);
pushup(p);
}
int merge(int x,int y)
{
if(!x||!y) return x+y;
int p;
if(t[x].dat>t[y].dat) t[x].r=merge(t[x].r,y),pushup(p=x);
else t[y].l=merge(x,t[y].l),pushup(p=y);
return p;
}
void insert(int &p,int k,int d)
{
t[++tot].val=d,t[tot].size=1,t[tot].dat=rand(),t[tot].hash=d;
int x,y;
split(p,k,x,y);
p=merge(merge(x,tot),y);
}
void change(int &p,int k,int d)
{
t[++tot].val=d,t[tot].size=1,t[tot].dat=rand(),t[tot].hash=d;
int x,y,is;
split(p,k,x,y),split(x,k-1,x,is);
p=merge(merge(x,tot),y);
}
int ask(int p,int ll,int rr)
{
int x,y,is;
ull ans;
split(p,rr,x,y),split(x,ll-1,x,is);
ans=t[is].hash;
p=merge(merge(x,is),y);
return ans;
}
bool check(int x,int y,int mid)
{
return ask(root,x,x+mid-1)==ask(root,y,y+mid-1);
}
int ask(int x,int y)
{
int ll=0,rr=min(n-x+1,n-y+1),mid,ans=0;
while(ll<=rr)
{
mid=(ll+rr)>>1;
if(check(x,y,mid)) ans=mid,ll=mid+1;
else rr=mid-1;
}
return ans;
}
void pre()
{
b[0]=1,b[1]=233;
for(int i=2;i<=N-1;i++) b[i]=b[i-1]*b[1];
}
signed main()
{
srand(time(0));
cin>>s+1; read(m);
n=strlen(s+1);
pre();
for(int i=1;i<=n;i++)
insert(root,i-1,s[i]-'a'+1);
while(m--)
{
char op,d; int x,y;
cin>>op;
if(op=='Q')
{
read(x),read(y);
if(x>y) swap(x,y);
write(ask(x,y)),puts("");
}
if(op=='R')
read(x),cin>>d,
change(root,x,d-'a'+1);
if(op=='I')
read(x),cin>>d,
insert(root,x,d-'a'+1),
n++;
}
}
AtCoder abc331_f Palindrome Query
類似的二分答案即可,需要存正串和反串,同時存線上段樹解法,不過多展開。
Palindrome Query
#include<bits/stdc++.h>
// #define int long long
#define ull unsigned long long
#define endl '\n'
#define sort stable_sort
#define f t[p]
#define ls t[t[p].ch[0]]
#define rs t[t[p].ch[1]]
#define l ch[0]
#define r ch[1]
using namespace std;
const int N=2e6+10,inf=0x7f7f7f7f;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int n,m,root,tot;
ull b[N];
char s[N];
struct fhq_treap
{
int ch[2],val,size,dat;
ull hash[2];
}t[N];
void pushup(int p)
{
f.size=ls.size+rs.size+1;
f.hash[0]=ls.hash[0]*b[rs.size+1]+f.val*b[rs.size]+rs.hash[0];
f.hash[1]=rs.hash[1]*b[ls.size+1]+f.val*b[ls.size]+ls.hash[1];
}
void split(int p,int k,int &x,int &y)
{
if(!p) {x=y=0; return ;}
if(ls.size>=k) y=p,split(f.l,k,x,t[y].l);
else x=p,split(f.r,k-ls.size-1,t[x].r,y);
pushup(p);
}
int merge(int x,int y)
{
if(!x||!y) return x+y;
int p;
if(t[x].dat>t[y].dat) t[x].r=merge(t[x].r,y),pushup(p=x);
else t[y].l=merge(x,t[y].l),pushup(p=y);
return p;
}
void insert(int &p,int k,int d)
{
t[++tot].val=d,t[tot].size=1,t[tot].dat=rand(),t[tot].hash[0]=t[tot].hash[1]=d;
int x,y;
split(p,k,x,y);
p=merge(merge(x,tot),y);
}
void change(int &p,int k,int d)
{
t[++tot].val=d,t[tot].size=1,t[tot].dat=rand(),t[tot].hash[0]=t[tot].hash[1]=d;
int x,y,is;
split(p,k,x,y),split(x,k-1,x,is);
p=merge(merge(x,tot),y);
}
int ask(int p,int ll,int rr,bool d)
{
int x,y,is;
ull ans;
split(p,rr,x,y),split(x,ll-1,x,is);
ans=t[is].hash[d];
p=merge(merge(x,is),y);
return ans;
}
void ask(int ll,int rr)
{
if(ll==rr) {puts("Yes"); return ;}
int mid=(rr-ll+1)>>1;
puts(ask(root,ll,ll+mid-1,0)==ask(root,rr-mid+1,rr,1)?"Yes":"No");
}
void pre()
{
b[0]=1,b[1]=233;
for(int i=2;i<=N-1;i++) b[i]=b[i-1]*b[1];
}
signed main()
{
srand(time(0));
read(n),read(m);
pre();
cin>>s+1;
for(int i=1;i<=n;i++) insert(root,i-1,s[i]-'a'+1);
while(m--)
{
int op,x,y; char d;
read(op);
if(op==1) read(x),cin>>d,change(root,x,d-'a'+1);
else read(x),read(y),ask(x,y);
}
}