後綴平衡樹是一種動態維護後綴排序的數據結構。
具體而言,它支持在串\(S\)的開頭添加/刪除一個字符。前端
重量平衡樹保證操做影響的最大子樹大小是最壞的或均攤的或指望的\(O(logn)\)。node
替罪羊樹依賴於一種暴力重構的操做。
它規定了一個平衡因子\(\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\)映射到實數\(\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\)。
在平衡樹上單點插入,考慮如何比較這個新後綴和其餘後綴的順序關係。ui
一個簡單粗暴的想法是二哈(二分+哈希)求\(lcp\)以後進行判斷,
由於須要比較\(O(logn)\)次,因此這樣作的複雜度爲\(O(log^2n)\)。spa
假設咱們要拿\(cS\)這個新後綴和一個後綴\(T\)作比較。
一個頗有用的條件是後綴\(S\)的排名是已知的。
假設\(T\)能夠表示爲\(c'T'\),那麼咱們至關於比較兩個二元組\((c,S),(c',T')\)的大小。
比較兩個字符的時間固然是\(O(1)\)。
因爲\(S\)和\(T'\)的排名都是已經維護好的,所以比較這兩個後綴的時間也是\(O(1)\)。
所以咱們將比較的時間優化到了\(O(1)\),那麼插入的時間複雜度變爲\(O(logn)\)。
這種方法不管是在編程複雜度仍是時間複雜度上都優於二分+哈希。code
給定\(S\)和數個\(T\),每次詢問\(T\)在\(S\)中出現了幾回。
由於已經後綴排序,只要找到第一個嚴格小於\(T\)的最後一個後綴和嚴格大於\(T\)的第一個後綴便可。
匹配時直接暴力。總複雜度爲\(O((|S|+\sum|T|)log|S|)\)。排序
查詢前驅/後繼,維護子樹和便可。
若是隻是求這個的話,直接set維護便可,根本不須要用到後綴平衡樹了。
論文題。
給定一個字符串\(S\),如今要求支持前端,後端,正中間,插入/刪除,
以及詢問一個串\(T\)在\(S\)中的出現次數。
咱們知道:
對於串的鏈接部分,能夠知道長度最多爲\(2(|T|-1)\),所以直接摳下來\(kmp\)便可。
時間複雜度爲\(O((|S|+\sum|T|)log|S|)\)。
參考資料:《重量平衡樹與後綴平衡樹在信息學競賽中的應用》