SAM

后缀自动机部分的练习。
毒奶(冬令营一定不会考SAM的)
然后就没了。
https://vjudge.net/contest/208780#overview
是一个感性理解比较容易的东西。
证明…可以去找clj的课件看。
有很多奇怪的性质,稍微记一下。

Problem A

应该是可以写O((n+Q)logn)或者O((n+Q)log^2n)的方法的..
O(n^2+Q),暴力对每一个右端点为结束的位置遍历一遍当前的SAM求答案。
然后差分。

Code
#include<bits/stdc++.h>
using namespace std;

#define Death Komachi
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define M 2004

char S[M];
int T,n,Ans[M][M];
struct SAM{
    static const int N=4004;
    int Trans[N][26],Slink[N],Mxlen[N],R[N],tot;
    void Clear(){
        memset(Trans,0,sizeof(Trans));
        memset(R,0,sizeof(R));
    }
    int Extend(int v,int c){
        int z=++tot;
        Mxlen[z]=Mxlen[v]+1;
        R[z]=R[v]+1;
        while(v && !Trans[v][c])Trans[v][c]=z,v=Slink[v];
        if(!v)Slink[z]=1;
        else {
            int x=Trans[v][c];
            if(Mxlen[v]+1==Mxlen[x])Slink[z]=x;
            else{
                int y=++tot;
                Slink[y]=Slink[x];
                Mxlen[y]=Mxlen[v]+1;
                R[y]=R[z];

                memcpy(Trans[y],Trans[x],sizeof(Trans[x]));
                while(Trans[v][c]==x)Trans[v][c]=y,v=Slink[v];

                Slink[x]=Slink[z]=y;
            }
        }
        return z;
    }
    int Q[N],Deg[N];
    void Build(char *S,int len){
        int p=tot=1;
        REP(i,0,len){
            p=Extend(p,S[i]-'a');
            int l,r;l=r=0;
            REP(j,2,tot+1)Deg[Slink[j]]++;
            REP(j,1,tot+1)if(!Deg[j])Q[r++]=j;

            while(l<r){
                int A=Q[l++],F=Slink[A];
                if(A==1)break;
                R[F]=max(R[F],R[A]);
                Ans[i][R[A]-Mxlen[A]]++,Ans[i][R[A]-Mxlen[F]]--;
                if(!(--Deg[F]))Q[r++]=F;
            }
            REP(j,1,i+1)Ans[i][j]+=Ans[i][j-1];
            DREP(j,i-1,-1)Ans[i][j]+=Ans[i][j+1];
        }
    }
}SAM;
int main(){
    scanf("%d",&T);
    while(T--){
        memset(Ans,0,sizeof(Ans));
        SAM.Clear();
        scanf("%s",S);
        n=strlen(S);
        SAM.Build(S,n);
        int Q,l,r;
        scanf("%d",&Q);
        while(Q--){
            scanf("%d%d",&l,&r);
            printf("%d\n",Ans[r-1][l-1]);
        }
    }
    return 0;
}
Problem C

LCS,将第二个串放在第一个串上跑一下即可。
然后就是SAM求前缀的LCP的过程。

Code
#include<bits/stdc++.h>
using namespace std;

#define Death Komachi
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define M 100004
#define LL long long

int n;
char S[M];
struct SAM{
    static const int N=M<<1;
    int Trans[N][26],Slink[N],Mxlen[N],tot;
    void Clear(){
        REP(i,1,tot+1)memset(Trans[i],0,sizeof(Trans[i]));
    }
    int Extend(int v,int c){
        int z=++tot;
        Mxlen[z]=Mxlen[v]+1;
        while(v && !Trans[v][c])Trans[v][c]=z,v=Slink[v];
        if(!v)Slink[z]=1;
        else {
            int x=Trans[v][c];
            if(Mxlen[x]==Mxlen[v]+1)Slink[z]=x;
            else{
                int y=++tot;
                Slink[y]=Slink[x];
                Mxlen[y]=Mxlen[v]+1;
                memcpy(Trans[y],Trans[x],sizeof(Trans[x]));
                while(Trans[v][c]==x)Trans[v][c]=y,v=Slink[v];
                Slink[x]=Slink[z]=y;
            }
        }
        return z;
    }
    void Build(char *S,int len){
        int p=tot=1;
        REP(i,0,len)p=Extend(p,S[i]-'a');
    }
    void Query(char *S,int len){
        int u=1,l=0,Ans=0;
        REP(i,0,len){
            int c=S[i]-'a';
            while(u && !Trans[u][c])u=Slink[u],l=Mxlen[u];
            if(u)u=Trans[u][c],l++;
            else u=1,l=0;
            Ans=max(Ans,l);
        }
        printf("%d\n",Ans);
    }
}SAM;
int main(){
    while(scanf("%s",S)!=EOF){
        SAM.Clear();
        SAM.Build(S,strlen(S));
        scanf("%s",S);
        SAM.Query(S,strlen(S));
    }
    return 0;
}
Problem G

查询当前串出现次数大于等于K的子串数目。
注意到Slink树上次数的单调性。
离线,用并查集来维护删点即可。

#include<bits/stdc++.h>
using namespace std;

#define Death Komachi
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define M 250004
#define LL long long

char S[M];
int n,K,m;
int Op[M],Pos[M];
LL Ans[M],Res;
struct SAM{
    static const int N=M<<1;
    int Trans[N][26],Slink[N],Mxlen[N],Sz[N],tot;
    void Clear(){
        REP(i,1,tot+1)memset(Trans[i],Sz[i]=Del[i]=0,sizeof(Trans[i]));
    }
    int Extend(int v,int c){
        int z=++tot;
        Sz[z]=1;
        Mxlen[z]=Mxlen[v]+1;
        while(v && !Trans[v][c])Trans[v][c]=z,v=Slink[v];
        if(!v)Slink[z]=1;
        else {
            int x=Trans[v][c];
            if(Mxlen[x]==Mxlen[v]+1)Slink[z]=x;
            else {
                int y=++tot;
                Slink[y]=Slink[x];
                Mxlen[y]=Mxlen[v]+1;
                memcpy(Trans[y],Trans[x],sizeof(Trans[x]));
                while(Trans[v][c]==x)Trans[v][c]=y,v=Slink[v];
                Slink[x]=Slink[z]=y;
            }
        }
        return z;
    }
    int Deg[N],Q[N],Fa[N],Del[N];
    int From(int a){return Fa[a]==a?a:Fa[a]=From(Fa[a]);}
    void Delete(int x){
        x=From(x),Del[x]++;
        while(1){
            int y=From(Slink[x]);
            if(Sz[x]-Del[x]<K){
                Res-=Mxlen[x]-Mxlen[Slink[x]];
                Del[y]+=Del[x];
                Fa[x]=y;
                x=y;
            }
            else break;
        }
    }
    void Build(char *S,int len){
        int p=tot=1;
        REP(i,0,len)Pos[i]=p=Extend(p,S[i]-'a');

        int l,r;l=r=0;

        REP(i,1,tot+1)Deg[Slink[i]]++,Fa[i]=i;
        REP(i,1,tot+1)if(!Deg[i])Q[r++]=i;

        while(l<r){
            int A=Q[l++],F=Slink[A];
            Sz[F]+=Sz[A];
            if(Sz[A]>=K)Res+=Mxlen[A]-Mxlen[F];
            else Fa[A]=F;
            if(!(--Deg[F]))Q[r++]=F;
        }
    }
}SAM;
int main(){
    while(scanf("%d%d%d",&n,&m,&K)!=EOF){
        SAM.Clear();
        Res=0;

        scanf("%s",S);

        REP(i,0,m){
            scanf("%d",&Op[i]);
            if(Op[i]==1)scanf("%s",S+n),n++;
        }
        SAM.Build(S,n);

        DREP(i,m-1,-1)if(Op[i]==1)SAM.Delete(Pos[--n]);
        else Ans[i]=Res;

        REP(i,0,m)if(Op[i]==2)printf("%lld\n",Ans[i]);
    }
    return 0;
}
Problem K

求字典序第K大的子串,强制在线。
SA+二分其实挺好写的。。
SAM:
首先将反转串建SAM,那么此时的Slink树为后缀树,
每一个节点就对应了连续的一段区间的子串。
用最后一个字符确定一下节点顺序,二分求答案在哪个节点里即可。

Code
#include<bits/stdc++.h>
using namespace std;

#define Death Komachi
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define M 100004
#define LL long long

static const int N=M<<1;
int n;
char S[M];
vector<int>E[N];
struct SAM{
    int Trans[N][26],Slink[N],Mxlen[N],R[N],tot;
    void Clear(){
        REP(i,1,tot+1)memset(Trans[i],R[i]=0,sizeof(Trans[i])),E[i].clear();
    }
    int Extend(int v,int c){
        int z=++tot;
        Mxlen[z]=Mxlen[v]+1;
        R[z]=R[v]+1;
        while(v && !Trans[v][c])Trans[v][c]=z,v=Slink[v];
        if(!v)Slink[z]=1;
        else {
            int x=Trans[v][c];
            if(Mxlen[x]==Mxlen[v]+1)Slink[z]=x;
            else {
                int y=++tot;
                Slink[y]=Slink[x];
                Mxlen[y]=Mxlen[v]+1;
                memcpy(Trans[y],Trans[x],sizeof(Trans[x]));
                while(Trans[v][c]==x)Trans[v][c]=y,v=Slink[v];
                Slink[x]=Slink[z]=y;
            }
        }
        return z;
    }
    void DFS(int A){
        int B;
        REP(i,0,E[A].size()){
            DFS(B=E[A][i]);
            R[A]=max(R[A],R[B]);
        }
    }
    void Build(char *S,int len){
        int p=tot=1;
        REP(i,0,len)p=Extend(p,S[i]-'a');
        REP(i,1,tot+1)E[Slink[i]].push_back(i);
        DFS(1);
    }
    inline int Pos(int x){
        return R[x]-Mxlen[Slink[x]]-1;
    }
}SAM;
LL L[N],Mxs;
int D[N];
bool Cmp1(int a,int b){return L[a]<L[b];}
bool Cmp2(int a,int b){return S[SAM.Pos(a)]<S[SAM.Pos(b)];}
void DFS(int A){
    Mxs+=SAM.Mxlen[A]-SAM.Mxlen[SAM.Slink[A]];
    L[A]=Mxs;
    sort(E[A].begin(),E[A].end(),Cmp2);
    REP(i,0,E[A].size())DFS(E[A][i]);
}
void Build(){
    Mxs=0;
    DFS(1);
    REP(i,1,SAM.tot+1)D[i]=i;
    sort(D+1,D+SAM.tot+1,Cmp1);
//  REP(i,1,SAM.tot+1)cerr<<D[i]<<' ';cerr<<endl;
//  REP(i,1,SAM.tot+1)cerr<<SAM.R[D[i]]<<' ';cerr<<endl;
//  REP(i,1,SAM.tot+1)cerr<<L[D[i]]<<' ';cerr<<endl;
}
void Query(int &l,int &r,LL k){
    if(k>Mxs)l=r=0;
    else {
        L[0]=k;
        int p=lower_bound(D+1,D+SAM.tot+1,0,Cmp1)-D;
        l=SAM.R[D[p]],r=l-k+L[D[p-1]]-SAM.Mxlen[SAM.Slink[D[p]]]+1;
        l=n-l+1,r=n-r+1;
    }
    printf("%d %d\n",l,r);
}
int main(){
    while(scanf("%s",S)!=EOF){
        n=strlen(S);
        reverse(S,S+n);
        SAM.Clear();
        SAM.Build(S,n);
        Build();

        int q,l,r;
        l=r=0;
        LL k;
        scanf("%d",&q);
        while(q--){
            scanf("%lld",&k);
            k=(k^l^r)+1;
            Query(l,r,k);
        }
    }
    return 0;
}
Problem M

每次加入一个字符,
相当于将一条链上的点i的权值加上Mxlen[i]-Mxlen[Slink[i]]。
用LCT维护权值,然后查询每一个差值,差值即插入点到根的权值和。
对新建y的地方要特殊处理,比较难调。

Code
#include<bits/stdc++.h>
using namespace std;

#define Death Komachi
#define REP(i,a,b) for(int i=(a),i##_end_=(b);i<i##_end_;i++)
#define DREP(i,a,b) for(int i=(a),i##_end_=(b);i>i##_end_;i--)
#define M 100004
#define LL long long
#define Mod 1000000007

static const int N=M<<1;
int n;
char S[M];
struct LCT{
    int Fa[N],Ch[N][2],Val[N],Sum[N],Add[N],Num[N],Res[N];
    void UpAdd(int x,int t){
        if(!x)return;
        Res[x]=(Res[x]+1ll*t*Sum[x])%Mod;
        Num[x]=(Num[x]+t)%Mod;
        Add[x]=(Add[x]+t)%Mod;
    }
    void Up(int x){
        Sum[x]=(0ll+Sum[Ch[x][0]]+Sum[Ch[x][1]]+Val[x])%Mod;
        Res[x]=(Res[Ch[x][0]]+Res[Ch[x][1]]+1ll*Num[x]*Val[x])%Mod;
    }
    void Down(int x){
        if(Add[x]){
            UpAdd(Ch[x][0],Add[x]);
            UpAdd(Ch[x][1],Add[x]);
            Add[x]=0;
        }
    }
    #define isroot(x) (Ch[Fa[x]][0]!=x && Ch[Fa[x]][1]!=x)
    void Rotate(int x){
        int y=Fa[x],z=Fa[y],R=Ch[y][0]==x,&d=Ch[x][R];
        Ch[y][!R]=d;
        if(d)Fa[d]=y;
        if(!isroot(y))Ch[z][Ch[z][1]==y]=x;
        d=y,Fa[x]=z,Fa[y]=x;
        Up(y);
    }
    int Stk[N];
    void Splay(int x){
        int top=0;
        for(int t=x;!isroot(t);t=Fa[t])
            Stk[++top]=Fa[t];
        while(top)Down(Stk[top--]);
        Down(x);
        while(!isroot(x)){
            int y=Fa[x];
            if(!isroot(y))Rotate((Ch[Fa[y]][0]==y)!=(Ch[y][0]==x)?x:y);
            Rotate(x);
        }
        Up(x);
    }
    void Access(int x){
        for(int t=0;x;t=x,x=Fa[x])
            Splay(x),Ch[x][1]=t,Up(x);
    }
    void Change(int x,int v,int t){
        Val[x]=v,Num[x]=t,Up(x);
    }
    void Updata(int x,int t){
        Access(x),Splay(x),UpAdd(Ch[x][0],t);
    }
    int Query(int x){
        Access(x),Splay(x);
        return Res[x];
    }
    void Link(int x,int y){
        Fa[x]=y;
    }
    void Cut(int x,int y){
        Access(x),Access(y),Fa[x]=0;
    }
}LCT;
struct SAM{
    int Trans[N][26],Slink[N],Mxlen[N],tot,Ans,Last;
    int Extend(int v,int c){
        int z=++tot;
        Mxlen[z]=Mxlen[v]+1;
        while(v && !Trans[v][c])Trans[v][c]=z,v=Slink[v];
        if(!v)Slink[z]=1;
        else {
            int x=Trans[v][c];
            if(Mxlen[x]==Mxlen[v]+1)Slink[z]=x;
            else {
                int y=++tot;
                Slink[y]=Slink[x];
                Mxlen[y]=Mxlen[v]+1;

                memcpy(Trans[y],Trans[x],sizeof(Trans[x]));
                while(Trans[v][c]==x)Trans[v][c]=y,v=Slink[v];

                LCT.Cut(x,Slink[x]);
                Slink[x]=Slink[z]=y;

                LCT.Change(y,Mxlen[y]-Mxlen[Slink[y]],LCT.Num[x]);
                LCT.Change(x,Mxlen[x]-Mxlen[y],LCT.Num[x]);

                LCT.Link(y,Slink[y]);
                LCT.Link(x,y);
            }
        }
        LCT.Change(z,Mxlen[z]-Mxlen[Slink[z]],0);
        LCT.Link(z,Slink[z]);
        LCT.Updata(z,1);

        int Tmp=((2ll*Ans-Last+LCT.Query(z))%Mod+Mod)%Mod;
        Last=Ans,Ans=Tmp;
        printf("%d\n",Ans);
        return z;
    }
    void Build(char *S,int len){
        int Ans=0,p=tot=1;
        REP(i,0,len)p=Extend(p,S[i]-'a');
    }
}SAM;
int main(){
    scanf("%d%s",&n,S);
    SAM.Build(S,n);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值