SuffixArray

来源:互联网 发布:人工智能简介ppt英文 编辑:程序博客网 时间:2024/06/02 05:55

后缀数组。
https://vjudge.net/contest/203023#overview
大约使用基数排序实现的O(nlogn)建立方法。
DC3比较麻烦就先不学了。
Hash+直接排序的方法在10行内完成。
复杂度为O(nlog2n)
算是也可以用吧。

Problem C

求不同的子串个数。
根据SA的定义,可以比较简单地求出重复的子串个数,即Height值的和。

Code

struct SA{    int Ht[M<<1],Rk[M<<1],sa[M];    int C[M];    void Solve(){        int *x=Rk,*y=Ht;        memset(Cnt,0,sizeof(Cnt));        memset(Rk,0,sizeof(Rk));        memset(Ht,0,sizeof(Ht));        REP(i,0,n)Cnt[x[i]=C[i]]++;        REP(i,1,M)Cnt[i]+=Cnt[i-1];        REP(i,0,n)sa[--Cnt[x[i]]]=i;        for(int k=1;k<=n;k<<=1){            int p=0;            REP(i,0,n)Cnt[x[sa[i]]]=i+1;            REP(i,n-k,n)y[p++]=i;            REP(i,0,n)if(sa[i]>=k)y[p++]=sa[i]-k;            DREP(i,n-1,-1)sa[--Cnt[x[y[i]]]]=y[i];            swap(x,y);            x[sa[0]]=1;            REP(i,1,n)x[sa[i]]=x[sa[i-1]]+            (y[sa[i-1]]!=y[sa[i]] || y[sa[i-1]+k]!=y[sa[i]+k]);        }        int p=0,j;        memset(Rk,0,sizeof(Rk));        REP(i,0,n)Rk[sa[i]]=i;        REP(i,0,n)if(j=Rk[i]){            if(p)p--;j=sa[j-1];            while(C[i+p]==C[j+p])p++;            Ht[Rk[i]]=p;        }        Ht[0]=INF;    }    LL SumH(){        LL Res=0;        REP(i,1,n)Res+=Ht[i];        return Res;    }}SA;

Problem D

最长回文子串的SA写法。
将原串改为str+‘$’+rev(str)。
分类讨论求某两个位置的LCP。
ST表求RMQ得LCP。

Code

struct SA{    int Ht[M<<1],Rk[M<<1],sa[M],RMQ[M][K];    char C[M];    int LCP(int l,int r){        if(l>r)swap(l,r);        l++;int k=Nm[r-l+1];//Nm[i]=log2i        return min(RMQ[l][k],RMQ[r-(1<<k)+1][k]);    }    void ReBuild(){        REP(i,0,n)RMQ[i][0]=Ht[i];        REP(k,1,K)REP(i,0,n)if((j=i+(1<<k-1))<n){            RMQ[i][k]=min(RMQ[i][k-1],RMQ[j][k-1]);        }else break;    }    void Answer(){        int Ans=0,Pos=-1;        REP(i,0,m){            int l=Query(Rk[i],Rk[n-i-1]);            if(chkmax(Ans,(l<<1)-1))Pos=i-l+1;        }        REP(i,1,m)if(C[i]==C[i-1]){            int l=Query(Rk[i],Rk[n-i]);            if(chkmax(Ans,l<<1))Pos=i-l;        }        REP(i,Pos,Pos+Ans)putchar(C[i]);        puts("");    }}SA;

Problem F

最大连续重复次数子串,字典序最小。
枚举长度,那么该串一定包含相邻该长度距离的两个字符。
分别求这两个字符向前向后的LCP,
即可得到重复次数,然后再通过对Rank值的RMQ求到字典序最小。
加了一些其他的优化。

Code

inline int Cmp(const int &a,const int &b){    return SA1.Rk[a]<SA1.Rk[b]?a:b;}int RKQ[M][K];inline int Query(int l,int r){    int k=Nm[r-l+1];    return Cmp(RKQ[l][k],RKQ[r-(1<<k)+1][k]);}void Answer(){    int j;    REP(k,1,K) REP(i,0,n) if((j=i+(1<<k-1))<n)        RKQ[i][k]=Cmp(RKQ[i][k-1],RKQ[j][k-1]);    int Ans=1,Pos=SA1.sa[0],Len=1;    REP(Lth,1,(n>>1)+1){        for(int i=0;i+Lth<n;){            int x,y=0;            if(SA1.C[i]==SA1.C[i+Lth]){                x=SA2.LCP(n-i-1,n-i-Lth-1),y=SA1.LCP(i,i+Lth);                int Tmp=(x+y-1)/Lth+1;                if(Tmp>=Ans){                    int Ltp=Tmp*Lth,ps=Query(i-x+1,i+y+Lth-Ltp);                    if(Ans==Tmp){if(SA1.Rk[ps]<SA1.Rk[Pos])Pos=ps,Len=Ltp;}                    else Ans=Tmp,Pos=ps,Len=Ltp;                }            }            i+=max(Lth,y);        }    }    printf("Case %d: ",++Case);    REP(i,Pos,Pos+Len)putchar(SA1.C[i]);    puts("");}

Problem G

最长公共子串。
即分属不同子串的相邻两个串的最大Height值。

Code

struct SA{    void Answer(){        int Ans=0;        REP(i,1,n)if(Ans<Ht[i] && ((sa[i-1]<m)!=(sa[i]<m)) )            Ans=Ht[i];        printf("%d\n",Ans);    }}SA;int main(){    scanf("%s",SA.C);    SA.C[m=strlen(SA.C)]='$';    scanf("%s",SA.C+m+1);    n=strlen(SA.C);    SA.Solve();    SA.Answer();    return 0;}

Problem L

求一段子串的不同子串的个数。
在询问时保证相邻两个串的字典序,求其LCP并减去。

Code

#define IN(x) (l<=x && x<=r)    void Answer(){        int l,r;        scanf("%d%d",&l,&r);l--,r--;        int Len=r-l+1,Ans=Len*(Len+1)>>1,Lt=-1;        REP(i,0,n)if(IN(sa[i])){            if(Lt==-1)Lt=i;            else{                int Tmp=LCP(Lt,i);                int la=r-sa[Lt]+1,lb=r-sa[i]+1;                Ans-=min(min(la,lb),Tmp);                if(la<lb || Tmp<lb)Lt=i;                if(!(--Len))break;            }        }        printf("%d\n",Ans);    }

Problem N

max(LCP(Rk[i],Rk[j]))(j<i)
不会写就暴力。
二分答案,然后二分当前区间,求Rk最小值判断是否成立。

int Tmp;    bool Check(int Len,int i){        if(!Len)return 1;        int a,Lt,Rt,l,r;        a=Lt=Rt=Rk[i];        l=0,r=a-1;        while(l<=r){            int Mid=l+r>>1;            if(Query(RMQ,Mid+1,a)>=Len)Lt=Mid,r=Mid-1;            else l=Mid+1;        }        l=a+1,r=n;         while(l<=r){            int Mid=l+r>>1;            if(Query(RMQ,a+1,Mid)>=Len)Rt=Mid,l=Mid+1;            else r=Mid-1;        }        Tmp=Query(MK,Lt,Rt);        return Tmp<i;    }    void Answer(){        for(int i=0;i<n;){            int l=0,r=n,Res=0;            while(l<=r){                int Mid=l+r>>1;                if(Check(Mid,i))Res=Mid,l=Mid+1;                else r=Mid-1;            }            if(!Res)printf("-1 %d\n",C[i]),i++;            else Check(Res,i),printf("%d %d\n",Res,Tmp),i+=Res;        }    }

然后正常的写法应该是单调栈。
i<j&&Ht[i]Ht[j]时,保留i显然更优。