[学习笔记]后缀平衡树

后缀数组+平衡树=后缀平衡树

支持动态插入字符(只能往前插入),即插入一个后缀,维护所有后缀的排名

插入后缀找到位置?平衡树上二分

法一:

哈希+二分,太慢

法二:

第一个字符不同,已经可以比较,否则比较第二个字符开始的后缀,之前这两个后缀排名已经处理好了。直接比较排名即可。

 

查询任意位置的排名?

法一:

暴力这个点往上跳log次,找到rank

法二:

每个点打一个tag,把排名“绝对化”

记录[l,r],val,表示x子树的tag值域区间和x的值val,val=(l+r)/2

这样可以O(1)查排名

 

总值域是[1,inf],深度logn,所以开个double一定精度没有问题。

具体用重量平衡树维护(子树大小期望logn,如treap或者SGT),SGT就可以啦

这样直接重构的时候把[l,r]tag重新赋值。(treap旋转时候必须直接重构。否则tag就乱了)

 

 

优点:

相较于LCT+SAM,可以离线,可以可持久化

 

 

 

例题

都挺裸的。。。

bzoj3682: Phorni

线段树维护区间字典序最小的id即可

注意:

由于tag或者排名是不断变化的,不能记录tag的值,只要记录id是哪一个即可。

因为新插入后缀,相对排名不影响

改变某一个pos[x]只会影响到根的结果。

pushup时候现场比较

#include<bits/stdc++.h>
#define reg register int
#define il inline
#define fi first
#define se second
#define mk(a,b) make_pair(a,b)
#define numb (ch^'0')
#define pb push_back
#define solid const auto &
#define enter cout<<endl
#define pii pair<int,int>
using namespace std;
typedef long long ll;
template<class T>il void rd(T &x){
    char ch;x=0;bool fl=false;
    while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
    for(x=numb;isdigit(ch=getchar());x=x*10+numb);
    (fl==true)&&(x=-x);
}
template<class T>il void output(T x){if(x/10)output(x/10);putchar(x%10+'0');}
template<class T>il void ot(T x){if(x<0) putchar('-'),x=-x;output(x);putchar(' ');}
template<class T>il void prt(T a[],int st,int nd){for(reg i=st;i<=nd;++i) ot(a[i]);putchar('\n');}

namespace Miracle{
const int N=500000+5;
const int M=1000000+5;
const double inf=1e18;
const double alp=0.75;
#define mid ((l+r)>>1)
#define Md ((L+R)/2.0)
int n,q,m,typ;
int pos[N];
char s[M];
int rt;
namespace SGT{
struct node{
    int ch[2];
    int sz;
    double l,r;
    double val;
    void init(double L,double R){
        sz=1;ch[0]=ch[1]=0;l=L;r=R;val=(L+R)/2;
    }
}t[M];
int tot;
#define ls t[x].ch[0]
#define rs t[x].ch[1]
void pushup(int x){
    if(!x) return;
    t[x].sz=t[ls].sz+t[rs].sz+1;
}
bool isbad(int x){
    return (t[ls].sz>t[x].sz*alp||t[rs].sz>t[x].sz*alp);
}
int q[M],num;
void dfs(int x){
    if(!x) return;
    dfs(ls);
    q[++num]=x;
    dfs(rs);
}
int build(int l,int r,double L,double R){
    if(l>r) return 0;
    int x=q[mid];
    t[x].init(L,R);
    ls=build(l,mid-1,L,Md);
    rs=build(mid+1,r,Md,R);
    pushup(x);
    return x;
}
void rebuild(int &x){
    num=0;dfs(x);
    x=build(1,num,t[x].l,t[x].r);
}
int sta[M],top;
void che(){
    for(reg i=1;i<=top;++i){
        if(isbad(sta[i])){
            if(i==1) rebuild(rt);
            else rebuild(t[sta[i-1]].ch[t[sta[i-1]].ch[1]==sta[i]]),pushup(sta[i-1]);
            return;
        }
    }
}
bool cmp(int x,int y){//x<y?
    if(s[x]!=s[y]) return s[x]<s[y];
    return t[x-1].val<t[y-1].val;
}
void ins(int p){//x is pos
    ++tot;
    if(!rt){
        rt=tot;
        t[tot].init(1,inf);
        return;
    }
    int x=rt;
    top=0;
    while(1){
        int d=cmp(x,p);
        sta[++top]=x;
        ++t[x].sz;
        if(!t[x].ch[d]){
            t[x].ch[d]=tot;
            if(d==1){//rson
                t[tot].init(t[x].val,t[x].r);
            }else{
                t[tot].init(t[x].l,t[x].val);
            }
            break;
        }
        x=t[x].ch[d];
    }
    che();
}
void pb(int x){
    ins(x);
}
#undef ls 
#undef rs 
}
namespace SMT{
#define ls (x<<1)
#define rs (x<<1|1)
struct node{
    int id;
}t[4*N];
int chm(int x,int y){
    return SGT::t[pos[x]].val<=SGT::t[pos[y]].val?x:y;
}
void pushup(int x){
    t[x].id=chm(t[ls].id,t[rs].id);
}
void build(int x,int l,int r){
    if(l==r){
        t[x].id=l;return;
    }
    build(ls,l,mid);
    build(rs,mid+1,r);
    pushup(x);
} 
void upda(int x,int l,int r,int p){
    if(l==r){
        t[x].id=l;return;
    }
    if(p<=mid) upda(ls,l,mid,p);
    else upda(rs,mid+1,r,p);
    pushup(x);
}
int query(int x,int l,int r,int L,int R){
    if(L<=l&&r<=R){
        return t[x].id;
    }
    if(L>mid) return query(rs,mid+1,r,L,R);
    if(R<=mid) return query(ls,l,mid,L,R);
    return chm(query(ls,l,mid,L,R),query(rs,mid+1,r,L,R));
}

}
int main(){
    rd(n);rd(q);rd(m);rd(typ);
    scanf("%s",s+1);
    reverse(s+1,s+m+1);
    for(reg i=1;i<=n;++i){
        rd(pos[i]);
    }
    for(reg i=1;i<=m;++i){
        SGT::pb(i);
    }
    SMT::build(1,1,n);
    char ch[233];
    int x,y;
    int lasans=0;
    while(q--){
        scanf("%s",ch);
        if(ch[0]=='I'){
            rd(x);
            if(typ) x=x^lasans;
            s[++m]='a'+x;
            SGT::pb(m);
        }else if(ch[0]=='C'){
            rd(x);rd(y);
            pos[x]=y;
            SMT::upda(1,1,n,x);
        }else if(ch[0]=='Q'){//warning!!!
            rd(x);rd(y);
            int now=SMT::query(1,1,n,x,y);
            lasans=now;
            printf("%d\n",lasans);
            //update lasans
        }
    }
    return 0;
}

}
signed main(){
    Miracle::main();
    return 0;
}

/*
   Author: *Miracle*
*/
View Code

 

例题2

维护一个栈,支持末尾添加删除字符
每次询问一个区间中,另一个输入的串的出现次数

• 𝑛 ≤ 5 × 10^5, ∑ 𝑠 ≤ 5 × 10

一个模式串在主串出现位置,一定是后缀数组sa数组的一段区间

可以在后缀平衡树上找到这个区间

找LCP用哈希+二分

 

但是是主串的一部分?

差分!变成前缀出现次数相减

可持久化后缀平衡树

但是还有删除字符,所以可持久化是树形结构而不是链,

每次还要倍增找到要差分的节点。

 

UOJ101

• 维护 𝑛 个字符串变量,要求支持
• 某个字符串末尾加字符
• 用一个字符串覆盖另一个字符串
• 询问 𝑖 有几个子串,可通过 𝑘 时刻的串 𝑗 在开头加上 𝑙, 𝑟 内的字符得到
• 询问 𝑖 有几个子串,可通过给出的串 𝑠 在开头加上 𝑙, 𝑟 内的字符得到
• 𝑛, 𝑚 ≤ 10^6,强制在线

 

和上一题差不多

第三第四个询问,就算是加上[l,r]字符,还是一个区间,依然可以暴力二分

还要可持久化

恶心恶心

 

posted @ 2019-05-11 15:05  *Miracle*  阅读(750)  评论(0编辑  收藏  举报