1. 程式人生 > >後綴平衡樹學習筆記

後綴平衡樹學習筆記

優化 本質 序列 替罪羊樹 alpha 以及 筆記 paste 字符串

後綴平衡樹簡介

後綴平衡樹是一種動態維護後綴排序的數據結構。
具體而言,它支持在串\(S\)的開頭添加/刪除一個字符。

前置知識-重量平衡樹

重量平衡樹保證操作影響的最大子樹大小是最壞的或均攤的或期望的\(O(logn)\)

不采用旋轉機制的重量平衡樹-替罪羊樹

替罪羊樹依賴於一種暴力重構的操作。
它規定了一個平衡因子\(\alpha\),需要保證對於每個節點有\(\alpha \times sz_x \ge sz_{ls_x},sz_{rs_x}\);
插入節點時,我們每次找出往上最高的不滿足平衡條件的節點,將其重構為完全二叉樹。

const double alpha=0.7;
int rt,tot,s[2][N],sz[N],cal[N],top;
void build(int &i,int L,int R){
  if(L>R)return;int M=(L+R)/2;i=cal[M];sz[i]=R-L+1;
  build(s[0][i],l,mid,L,M-1);build(s[1][i],mid,r,M+1,R);
}
void recycle(int&i){
    if(s[0][i])recycle(s[0][i]);cal[++top]=i;if(s[1][i])recycle(s[1][i]);i=0;
}
void rebuild(int &i){top=0;recycle(i);build(i,1,top);}
void insert(int &i,int val,int f){
    if(!i){i=newnode();v[i]=val;sz[i]=1;return;}
    sz[i]++;int fg=f;
    if(val<v[i])fg|=(alpha*sz[i]<=(sz[ls[i]]+1)),insert(s[0][i],val,fg);
    else fg|=(alpha*szu)rebuild(i);
}

刪除節點時,這裏使用的是merge左右子樹。
為什麽?因為zsy聚聚是這麽寫的啊
瞎遍一下復雜度:即使刪除後不滿足平衡條件,只要不做插入操作樹高也不會高於\(O(logn)\),
而進行插入操作我們就會重構子樹。
應該不會\(T\)...

inline void update(int i){sz[i]=sz[s[0][i]]+1+sz[s[1][i]];}
int merge(int a,int b){
    if(!a||!b)return a|b;
    if(sz[a]>sz[b])return s[1][a]=merge(s[1][a],b),update(a),a;
    else return s[0][b]=merge(a,s[0][b]),update(b),b;
}
inline void del(int &i,int p){//刪除節點p
    if(i==p){i=merge(s[0][i],s[1][i]);return;}
    sz[i]--;val[p]<val[i]?del(s[0][i],p):del(s[1][i],p);
}

重量平衡樹的一個應用:序列順序維護問題

給出一個節點序列,要求支持如下兩種操作:

  • \(x\)後插入新節點\(y\)
  • 詢問\(a,b\)的前後關系。

平衡樹模板題
我們考慮將節點\(x\)映射到實數\(\varphi(x)\),\(\varphi\)的大小關系分節點的順序關系。
建立平衡樹時,我們維護每棵子樹\(\varphi\)的取值區間\((l,r)\),規定根節點\(\varphi\)的取值區間為\((0,1)\)
假設當前節點維護的區間為\((l,r)\),那麽這個節點的\(\varphi\)可以當做\(\frac{l+r}{2}\),
左右兒子的區間分別為\((l,\frac{l+r}{2})\)

\((\frac{l+r}{2},r)\)
做完了?
我們發現\(\varphi\)的分母為\(2^{deep-1}\),而深度太深就會掉精度。
雖然我們知道平衡樹的深度是\(O(nlogn)\)的,
但這意味著當我們維護平衡樹的平衡時我們需要重新維護子樹內所有節點的\(\varphi\)值。
這時重量平衡樹就派上了用場。
因為重量平衡樹每次影響的子樹大小是\(O(nlogn)\)的,因此可以使用於此問題。
這樣我們做到了插入\(O(logn)\)查詢\(O(1)\)

後綴平衡樹的構造

\(S\)的前端插入字符\(c\),相當於加入了一個新後綴\(cS\)
在平衡樹上單點插入,考慮如何比較這個新後綴和其他後綴的順序關系。

哈希

一個簡單粗暴的想法是二哈(二分+哈希)求\(lcp\)之後進行判斷,
因為需要比較\(O(logn)\)次,所以這樣做的復雜度為\(O(log^2n)\)

套用序列順序問題

假設我們要拿\(cS\)這個新後綴和一個後綴\(T\)做比較。
一個很有用的條件是後綴\(S\)的排名是已知的。
假設\(T\)可以表示為\(c'T'\),那麽我們相當於比較兩個二元組\((c,S),(c',T')\)的大小。
比較兩個字符的時間當然是\(O(1)\)
由於\(S\)\(T'\)的排名都是已經維護好的,因此比較這兩個後綴的時間也是\(O(1)\)
因此我們將比較的時間優化到了\(O(1)\),那麽插入的時間復雜度變為\(O(logn)\)
這種方法無論是在編程復雜度還是時間復雜度上都優於二分+哈希。

後綴平衡樹的應用

字符串匹配

給定\(S\)和數個\(T\),每次詢問\(T\)\(S\)中出現了幾次。
因為已經後綴排序,只要找到第一個嚴格小於\(T\)的最後一個後綴和嚴格大於\(T\)的第一個後綴即可。
匹配時直接暴力。總復雜度為\(O((|S|+\sum|T|)log|S|)\)

求本質不同子串數

查詢前驅/後繼,維護子樹和即可。
如果只是求這個的話,直接set維護即可,根本不需要用到後綴平衡樹了。

STRQUERY by 陳立傑

論文題。
給定一個字符串\(S\),現在要求支持前端,後端,正中間,插入/刪除,
以及詢問一個串\(T\)\(S\)中的出現次數。
我們知道:

  • 一個後綴平衡樹只能支持一邊插入/刪除。
  • 兩個平衡樹就可以支持兩邊插入/刪除。
    其中一個刪完後將另外一個分成兩份,可以保證重構的代價\(\le\)操作次數。
  • 四個平衡樹就可以支持前端,後端,正中間,插入/刪除。
    對於正中間的位置將字符左右彈一下即可。

對於串的連接部分,可以知道長度最多為\(2(|T|-1)\),因此直接摳下來\(kmp\)即可。
時間復雜度為\(O((|S|+\sum|T|)log|S|)\)

Code

參考資料:《重量平衡樹與後綴平衡樹在信息學競賽中的應用》

後綴平衡樹學習筆記