后缀数组(无讲解)

Posted suika

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了后缀数组(无讲解)相关的知识,希望对你有一定的参考价值。

BZO2754: [SCOI2012]喵星球上的点名

题目链接

分析:

  • 把姓和名中间用一个分隔符分开,和询问串一起建立后缀数组。
  • 后缀数组上每个位置存对应串的标号。对于一个询问串(T),找到他对应的位置。
  • 考虑和他的lcp>=len(T)的位置都是合法的。左右二分/倍增提取出这样的区间。
  • 那么第一问转化成了区间颜色数量。树状数组即可。
  • 第二问相当于有若干次区间 只在第一次出现的位置 加。然后查询每个位置的和。方法类似,不过这次我们扫一遍序列。
  • 假设当前位置为i。先加入左端点是i的询问,在i处+1。然后处理这个点的信息。ans[id[i]] += [pre,i]的区间和(pre是上一次和i相等的位置)然后处理右端点等于i的询问,在该询问的左端点处-1。

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
#define rep(n) for(i=1;i<=n;i++)
#define per(n) for(i=n;i;i--)
#define N 500050
int w[N],sa[N],ht[N],rk[N],f[20][N],n,m,ln;
int bg[N],ed[N],ws[N],wa[N],wb[N],Lg[N],id[N];
int ql[N],qr[N],ans1[N],now[N],nxt[N],c[N],pid[N];
int ans2[N],ke[N];
void fix(int x,int v) {
    for(;x<=ln;x+=x&(-x)) c[x]+=v;
}
int inq(int x) {
    int re=0;
    for(;x;x-=x&(-x)) re+=c[x]; return re;
}
struct A {
    int l,r,id;
    bool operator < (const A &x) const {
        return l == x.l ? r < x.r : l < x.l;
    }
}q[N];
priority_queue<pair<int,int> >pq;
void build_sa(int n,int m) {
    int i,j,p,*x=wa,*y=wb;
    rep(m) ws[i]=0;
    rep(n) ws[x[i]=w[i]]++;
    rep(m) ws[i]+=ws[i-1];
    per(n) sa[ws[x[i]]--]=i;
    for(j=1;j<n;j<<=1) {
        p=0;
        for(i=n;i>n-j;i--) y[++p]=i;
        rep(n) if(sa[i]>j) y[++p]=sa[i]-j;
        rep(m) ws[i]=0;
        rep(n) ws[x[i]]++;
        rep(m) ws[i]+=ws[i-1];
        per(n) sa[ws[x[y[i]]]--]=y[i];
        swap(x,y);
        m=1;
        rep(n) {
            x[sa[i]]=(y[sa[i]]==y[sa[i+1]]&&y[sa[i]+j]==y[sa[i+1]+j])?m:m++;
        }
        if(m>n) break;
    }
    rep(n) rk[sa[i]]=i;
    p=0;
    rep(n) if(rk[i]!=n) {
        j=rk[i]+1;
        for(;w[i+p]==w[sa[j]+p];p++) ;
        ht[j]=p;
        if(p) p--;
    }
    Lg[0]=-1;
    rep(n) Lg[i]=Lg[i>>1]+1,f[0][i]=ht[i];
    for(i=1;(1<<i)<=n;i++) {
        for(j=1;j<=n-(1<<i)+1;j++) {
            f[i][j]=min(f[i-1][j],f[i-1][j+(1<<(i-1))]);
        }
    }
}
int gm(int l,int r) {
    int len=Lg[r-l+1];return min(f[len][l],f[len][r-(1<<len)+1]);
}
int lcp(int x,int y) {
    x=rk[x], y=rk[y];
    if(x>y) swap(x,y);
    if(x==y) return ln-sa[x]+1;
    return gm(x+1,y);
}
void prt(int *a) {
    int i;
    for(i=1;i<=ln;i++) printf("%d ",a[i]); puts("");
}
int main() {
    scanf("%d%d",&n,&m);
    int i,x,y,j;
    int tmp=10000;
    for(i=1;i<=n;i++) {
        scanf("%d",&x);
        bg[i]=ln+1;
        while(x--) {
            scanf("%d",&y); y++;
            w[++ln]=y;
        }
        w[++ln]=++tmp;
        
        scanf("%d",&x);
        while(x--) {
            scanf("%d",&y); y++;
            w[++ln]=y;
        }
        ed[i]=ln;
        w[++ln]=++tmp;
    }
    for(i=1;i<=m;i++) {
        scanf("%d",&x);
        bg[i+n]=ln+1;
        while(x--) {
            scanf("%d",&y); y++;
            w[++ln]=y;
        }
        ed[i+n]=ln;
        w[++ln]=++tmp;
    }
    build_sa(ln,tmp);
    for(i=1;i<=n;i++) for(j=bg[i];j<=ed[i];j++) id[j]=i;
    for(i=1;i<=ln;i++) pid[i]=id[sa[i]];
    // for(i=1;i<=m;i++) for(j=bg[i+n];j<=ed[i+n];j++) id[j]=i+n;
    //prt(id);
    //prt(w);
    for(i=1;i<=m;i++) {
        x=rk[bg[i+n]];
        int len=ed[i+n]-bg[i+n]+1;
        //printf("x=%d len=%d
",x,len);
        int l=1,r=x;
        while(l<r) {
            int mid=(l+r)>>1;
            if(x==mid||gm(mid+1,x)>=len) r=mid;
            else l=mid+1;
        }
        ql[i]=l;
        l=x,r=ln+1;
        while(l<r) {
            int mid=(l+r)>>1;
            if(x==mid||gm(x+1,mid)>=len) l=mid+1;
            else r=mid;
        }
        qr[i]=l-1;
        q[i]=(A){ql[i],qr[i],i};

        /*printf("Q%d
",i);
        printf("ql=%d, qr=%d
",ql[i],qr[i]);
        for(j=ql[i];j<=qr[i];j++) {
            printf("%d
",id[sa[j]]);
        }
        puts("");*/
    }
    sort(q+1,q+m+1);
    for(i=ln;i;i--) {
        nxt[i]=now[pid[i]];
        now[pid[i]]=i;
    }
    memset(now,0,sizeof(now));
    for(i=1;i<=ln;i++) {
        if(pid[i]&&!now[pid[i]]) {
            now[pid[i]]=1;
            fix(i,1);
        }
    }
    j=1;
    for(i=1;i<=m;i++) {
        for(;j<q[i].l;j++) {
            if(pid[j]) {
                fix(j,-1);
                if(nxt[j]) fix(nxt[j],1);
            }
        }
        ans1[q[i].id]=inq(q[i].r);
    }
    for(i=1;i<=m;i++) printf("%d
",ans1[i]);
    memset(now,0,sizeof(now));
    memset(c,0,sizeof(c));
    j=1;
    for(i=1;i<=ln;i++) {
        for(;q[j].l==i;j++) fix(i,1),pq.push(make_pair(-q[j].r,q[j].l));
        if(pid[i]) {
            ans2[pid[i]]+=inq(i)-inq(now[pid[i]]);
            now[pid[i]]=i;
        }
        while(!pq.empty()&&pq.top().first==-i) {
            fix(pq.top().second,-1); pq.pop();
        }
    }
    for(i=1;i<=n;i++) printf("%d ",ans2[i]);
}
/*
2 3 
6 8 25 0 24 14 8 6 18 0 10 20 24 0 
7 14 17 8 7 0 17 0 5 8 25 0 24 0 
4 8 25 0 24 
4 7 0 17 0 
4 17 0 8 25 
*/


BZOJ4556: [Tjoi2016&Heoi2016]字符串

题目链接

  • 二分答案mid。然后像上一道题那样,根据[c,d]找一个合法区间ht>=mid。是否存在一个位置的a<=sa[i]<=b-mid+1。主席树上查即可。

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <cmath>
#include <map>
#include <queue>
#include <stack>
#include <cctype>
#include <vector>
#include <set>
#include <string>
using namespace std;
#define N 100050
#define rep(n) for(i=1;i<=n;i++) 
#define per(n) for(i=n;i;i--) 
char w[N];
int rr[N],rk[N],sa[N],ht[N],f[20][N],n,m;
int ws[N],wa[N],wb[N],Lg[N],root[N];
int siz[N*30],ls[N*30],rs[N*30],cnt;
void update(int l,int r,int x,int &p,int q) {
    p=++cnt; ls[p]=ls[q]; rs[p]=rs[q]; siz[p]=siz[q]+1;
    if(l==r) return ;
    int mid=(l+r)>>1;
    if(x<=mid) update(l,mid,x,ls[p],ls[q]);
    else update(mid+1,r,x,rs[p],rs[q]);
}
void build_sa(int n,int m) {
    int i,j,p,*x=wa,*y=wb;
    rep(m)ws[i]=0;
    rep(n)ws[x[i]=rr[i]]++;
    rep(m)ws[i]+=ws[i-1];
    per(n)sa[ws[x[i]]--]=i;
    for(j=1;j<n;j<<=1) {
        p=0;
        for(i=n;i>n-j;i--) y[++p]=i;
        rep(n) if(sa[i]>j) y[++p]=sa[i]-j;
        rep(m) ws[i]=0;
        rep(n) ws[x[i]]++;
        rep(m) ws[i]+=ws[i-1];
        per(n) sa[ws[x[y[i]]]--]=y[i];
        swap(x,y); m=1;
        rep(n) {
            x[sa[i]]=(y[sa[i]]==y[sa[i+1]]&&y[sa[i]+j]==y[sa[i+1]+j])?m:m++;
        }
        if(m>n) break;
    }
    rep(n) rk[sa[i]]=i;
    p=0;
    rep(n) if(rk[i]!=n) {
        j=rk[i]+1;
        for(;w[i+p]==w[sa[j]+p];p++) ;
        ht[j]=p; 
        if(p) p--;
    }
    Lg[0]=-1;
    rep(n) Lg[i]=Lg[i>>1]+1,f[0][i]=ht[i];
    for(i=1;(1<<i)<=n;i++) {
        for(j=1;j+(1<<i)-1<=n;j++) {
            f[i][j]=min(f[i-1][j],f[i-1][j+(1<<(i-1))]);
        }
    }
}
int gm(int l,int r) {
    int len=Lg[r-l+1];
    return min(f[len][l],f[len][r-(1<<len)+1]);
}
int l1,l2,r1,r2,OK;
void query(int l,int r,int x,int y,int p,int q) {
    if(OK) return ;
    if(x<=l&&y>=r) {
        if(siz[q]-siz[p]>0) OK=1;
        return ;
    }
    int mid=(l+r)>>1;
    if(x<=mid) query(l,mid,x,y,ls[p],ls[q]);
    if(y>mid) query(mid+1,r,x,y,rs[p],rs[q]);
}
bool check(int mid) {
    int x=rk[l2];
    int d=0,p=x,ql,qr;
    for(d=1;x-d>=1&&gm(x-d+1,x)>=mid;d<<=1) ;
    for(d>>=1;d;d>>=1) {
        if(p-d>=1&&gm(p-d+1,x)>=mid) p-=d;
    }
    ql=p;

    d=0,p=x;
    for(d=1;x+d<=n&&gm(x+1,x+d)>=mid;d<<=1) ;
    for(d>>=1;d;d>>=1) {
        if(p+d<=n&&gm(x+1,p+d)>=mid) p+=d;
    }
    qr=p;

    OK=0;
    query(1,n,l1,r1-mid+1,root[ql-1],root[qr]);
    return OK;
}
int main() {
    scanf("%d%d%s",&n,&m,w+1);
    int i;
    for(i=1;i<=n;i++) rr[i]=w[i]-'a'+1;
    build_sa(n,255);
    rep(n) {
        update(1,n,sa[i],root[i],root[i-1]);
    }
    while(m--) {
        scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
        int l=0,r=min(r1-l1,r2-l2)+2;
        while(l<r) {
            int mid=(l+r)>>1;
            if(check(mid)) l=mid+1;
            else r=mid;
        }
        printf("%d
",l-1);
    }
}


BZOJ4199: [Noi2015]品酒大会

题目链接

分析:

  • 把sa[]按ht排序。并查集维护即可。
  • 维护连通块内方案数和最大值。由于有负数需要再维护一个最小值。

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
using namespace std;
#define N 300050
typedef long long ll;
#define rep(n) for(i=1;i<=n;i++) 
#define per(n) for(i=n;i;i--)
int rr[N],n,val[N],sa[N],rk[N],ht[N],ws[N],wa[N],wb[N];
int t[N],siz[N],fa[N];
ll ans1[N],ans2[N];
int mn[N],mx[N];
char w[N];
int find(int x) {return fa[x]==x?x:fa[x]=find(fa[x]);}
void build_sa(int n,int m) {
    int i,j,p,*x=wa,*y=wb;
    rep(m) ws[i]=0;
    rep(n) ws[x[i]=rr[i]]++;
    rep(m) ws[i]+=ws[i-1];
    per(n) sa[ws[x[i]]--]=i;
    for(j=1;j<n;j<<=1) {
        p=0;
        for(i=n;i>n-j;i--) y[++p]=i;
        rep(n) if(sa[i]>j) y[++p]=sa[i]-j;
        rep(m) ws[i]=0;
        rep(n) ws[x[i]]++;
        rep(m) ws[i]+=ws[i-1];
        per(n) sa[ws[x[y[i]]]--]=y[i];
        swap(x,y);
        m=1;
        rep(n) {
            x[sa[i]]=(y[sa[i]]==y[sa[i+1]]&&y[sa[i]+j]==y[sa[i+1]+j])?m:m++;
        }
        if(m>n) break;
    }
    rep(n) rk[sa[i]]=i;
    p=0;
    rep(n) if(rk[i]!=n) {
        j=rk[i]+1;
        for(;w[i+p]==w[sa[j]+p];p++) ;
        ht[j]=p;
        if(p) p--;
    }
}
inline bool cmp(const int &x,const int &y) {return ht[x]>ht[y];}
int main() {
    scanf("%d%s",&n,w+1);
    int i;
    rep(n) ans2[i]=-1ll<<60;
    for(i=1;i<=n;i++) scanf("%d",&val[i]);
    for(i=1;i<=n;i++) rr[i]=w[i]-'a'+1;
    build_sa(n,255);
    for(i=1;i<=n;i++) t[i]=i,fa[i]=i,mn[i]=mx[i]=val[sa[i]],siz[i]=1;
    sort(t+1,t+n+1,cmp);
    for(i=1;i<=n;i++) {
        int x=t[i],y=x-1;
        if(x==1) continue;
        int dx=find(x),dy=find(y);
        ans1[ht[x]]+=ll(siz[dx])*siz[dy];
        ans2[ht[x]]=max(ans2[ht[x]],max(ll(mn[dx])*mn[dy],ll(mx[dx])*mx[dy]));
        fa[dx]=dy; siz[dy]+=siz[dx]; mn[dy]=min(mn[dy],mn[dx]); mx[dy]=max(mx[dy],mx[dx]);
    }
    per(n) ans1[i-1]+=ans1[i],ans2[i-1]=max(ans2[i-1],ans2[i]);
    rep(n) printf("%lld %lld
",ans1[i-1],ans1[i-1]?ans2[i-1]:0);
}


BZOJ3230: 相似子串

分析:

  • 考虑sa数组每一位都对应一些本质不同字符串。这个肯定是按字典序来的。
  • 维护n-sa[i]+1-ht[i]的前缀和。每次询问二分即可。

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
using namespace std;
#define N 100050
typedef long long ll;
#define rep(n) for(i=1;i<=n;i++)
#define per(n) for(i=n;i;i--)
#define pi pair<int,int>
int n,m;
ll v[N],sv[N];
char w[N];
struct SA {
    int rr[N],sa[N],ht[N],ws[N],wa[N],wb[N],rk[N],Lg[N],f[20][N];
    void build_sa(int n,int m) {
        int i,j,p,*x=wa,*y=wb;
        rep(m) ws[i]=0;
        rep(n) ws[x[i]=rr[i]]++;
        rep(m) ws[i]+=ws[i-1];
        per(n) sa[ws[x[i]]--]=i;
        for(j=1;j<n;j<<=1) {
            p=0;
            for(i=n;i>n-j;i--) y[++p]=i;
            rep(n) if(sa[i]>j) y[++p]=sa[i]-j;
            rep(m) ws[i]=0;
            rep(n) ws[x[i]]++;
            rep(m) ws[i]+=ws[i-1];
            per(n) sa[ws[x[y[i]]]--]=y[i];
            swap(x,y);
            m=1;
            rep(n) x[sa[i]]=(y[sa[i]]==y[sa[i+1]]&&y[sa[i]+j]==y[sa[i+1]+j])?m:m++;
            if(m>n) break;
        }
        rep(n) rk[sa[i]]=i;
        p=0;
        rep(n) if(rk[i]!=n) {
            j=rk[i]+1;
            for(;rr[i+p]==rr[sa[j]+p];p++) ;
            ht[j]=p;
            if(p) p--;
        }
        Lg[0]=-1;
        rep(n) Lg[i]=Lg[i>>1]+1;
        rep(n) f[0][i]=ht[i];
        for(i=1;(1<<i)<=n;i++) for(j=1;j+(1<<i)-1<=n;j++) f[i][j]=min(f[i-1][j],f[i-1][j+(1<<(i-1))]);
    }
    int gm(int l,int r) {
        int len=Lg[r-l+1];
        return min(f[len][l],f[len][r-(1<<len)+1]);
    }
    pi getk(ll K) {
        int l=1,r=n+1;
        while(l<r) {
            int mid=(l+r)>>1;
            if(sv[mid]>=K) r=mid;
            else l=mid+1;
        }
        return make_pair(sa[l],int(n-sv[l]+K));
    }
    int LCP(int x,int y) {
        if(x==y) return n-x+1;
        x=rk[x],y=rk[y];
        if(x>y) swap(x,y);
        return gm(x+1,y);
    }
    int lcp(int l1,int r1,int l2,int r2) {
        int t=LCP(l1,l2);
        //printf("%d %d %d %d
",l1,r1,l2,r2);
        //printf("t=%d
",t);
        return min(t,min(r1-l1+1,r2-l2+1));
    }
}s1,s2;
ll SQ(ll x) {return x*x;}
int main() {
    scanf("%d%d%s",&n,&m,w+1);
    int i;
    rep(n) s1.rr[i]=w[i]-'a'+1;
    rep(n) s2.rr[i]=s1.rr[n-i+1];
    s1.build_sa(n,255);
    s2.build_sa(n,255);
    rep(n) {
        v[i]=n-s1.sa[i]+1-s1.ht[i];
        sv[i]=sv[i-1]+v[i];
    }
    while(m--) {
        ll x,y;
        scanf("%lld%lld",&x,&y);
        if(x>sv[n]||y>sv[n]) {
            puts("-1"); continue;
        }
        pi tx=s1.getk(x);
        pi ty=s1.getk(y);
        int l1=tx.first,r1=tx.second;
        int l2=ty.first,r2=ty.second;
        //printf("%d %d %d %d
",l1,r1,l2,r2);
        printf("%lld
",SQ(s1.lcp(l1,r1,l2,r2))+SQ(s2.lcp(n-r1+1,n-l1+1,n-r2+1,n-l2+1)));
    }
}

以上是关于后缀数组(无讲解)的主要内容,如果未能解决你的问题,请参考以下文章

初学后缀数组记录(然而并不是很会。。&&很水。。)

P3809 后缀排序(倍增法后缀数组)

P3809 后缀排序(倍增法后缀数组)

数据结构4——后缀数组

经典算法——KMP,深入讲解next数组的求解

后缀数组模板