后缀平衡树学习笔记

后缀平衡树简介

后缀平衡树是一种动态维护后缀排序的数据结构。
具体而言,它支持在串\(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\)后插入新节点\(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\)
在平衡树上单点插入,考虑如何比较这个新后缀和其余后缀的顺序关系。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维护便可,根本不须要用到后缀平衡树了。

STRQUERY by 陈立杰

论文题。
给定一个字符串\(S\),如今要求支持前端,后端,正中间,插入/删除,
以及询问一个串\(T\)\(S\)中的出现次数。
咱们知道:

  • 一个后缀平衡树只能支持一边插入/删除。
  • 两个平衡树就能够支持两边插入/删除。
    其中一个删完后将另一个分红两份,能够保证重构的代价\(\le\)操做次数。
  • 四个平衡树就能够支持前端,后端,正中间,插入/删除。
    对于正中间的位置将字符左右弹一下便可。

对于串的链接部分,能够知道长度最多为\(2(|T|-1)\),所以直接抠下来\(kmp\)便可。
时间复杂度为\(O((|S|+\sum|T|)log|S|)\)

Code

参考资料:《重量平衡树与后缀平衡树在信息学竞赛中的应用》

相关文章
相关标签/搜索