【模板整合计划】字符串

【模板整合计划】字符串

一:【字符串 Hash】

【模板】 兔子与兔子 \(\text{[CH1401]}\)

#include<iostream>
#include<string>
#include<cstdio>
#define LL long long
using namespace std;
const int N=1e6+5;
int l1,r1,l2,r2,n,m,i,p=27;
LL f[N],mi[N];string a;
inline LL Hash(int l,int r){return f[r]-f[l-1]*mi[r-l+1];}
int main(){
    cin>>a;n=a.size();a='$'+a;mi[0]=1;
    for(i=1;i<=n;i++)f[i]=f[i-1]*p+a[i]-'a'+1,mi[i]=mi[i-1]*p;
    scanf("%d",&m);
    while(m--){
        scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
        if(Hash(l1,r1)==Hash(l2,r2))printf("Yes\n");
        else printf("No\n");
    }
}

二:【最小表示法】

【模板】 最小表示法 / 工艺 \(\text{[P1368]}\)

#include<cstdio>
int i,j,k,n,st,s[600003];
inline void in(int &x){
    char c=getchar();
    while(c<'0'||c>'9')c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
}
inline void out(int x){if(x>9)out(x/10);putchar(x%10+'0');}
int main(){
    in(n);
    for(i=1;i<=n;s[i+n]=s[i],++i)in(s[i]);
    i=1,j=2;
    while(i<=n&&j<=n){
        k=0;
        while(k<n&&s[i+k]==s[j+k])++k;
        if(k==n)break;
        if(s[i+k]>s[j+k]){i+=k+1;if(i==j)++i;}
        else{j+=k+1;if(i==j)++j;}
    }
    st=i>j?j:i;
    for(i=st;i<st+n;++i)out(s[i]),putchar(' ');
}

三:【马拉车 (Manaher)】

【模板】 \(\text{Manacher}\) 算法 \(\text{[P3805]}\)

#include<algorithm>
#include<cstdio>
using namespace std;
const int N=22000003;
char a[N]={'$','|'},c=getchar();int r[N],mid,ans,R,n=1,i;
int main(){
    while(c>='a'&&c<='z')a[++n]=c,a[++n]='|',c=getchar();
    for(i=1;i<=n;i++){
        if(i<=R)r[i]=min(r[(mid<<1)-i],R-i+1);
        else r[i]=1;
        while(a[i-r[i]]==a[i+r[i]])r[i]++;
        if(i+r[i]-1>R)mid=i,R=i+r[i]-1;
        ans=max(ans,r[i]-1);
    }
    printf("%d",ans);
}

四:【Trie】

1.【标准版 Trie 树】

【模板】 于是他错误的点名开始了 \(\text{[P2580]}\)

#pragma once
#include<cstring>
#include<cstdio>
#define R register int
using namespace std;
const int SP=500003;
int trie[SP][26],pan[SP],ed[SP],re,t=1,n;
char ch[55];
inline void add(char ch[]){
    R a,i=0,p=1,m=strlen(ch);
    for(;i<m;i++){
        a=ch[i]-'a';
        if(!trie[p][a])trie[p][a]=++t;
        p=trie[p][a];
    }
    ed[p]=1;
}
inline int find(char ch[]){
    R re,i=0,p=1,m=strlen(ch);
    for(;i<m;i++){
        p=trie[p][ch[i]-'a'];
        if(!p)return 0;
    }
    re=ed[p];
    if(ed[p])ed[p]++;
    return re;
}
int main(){
    scanf("%d",&n);
    while(n--){scanf("%s",ch);add(ch);}
    scanf("%d",&n);
    while(n--){
        scanf("%s",ch);
        re=find(ch);
        if(!re)printf("WRONG\n");
        else if(re==1)printf("OK\n");
        else printf("REPEAT\n");
    }
}

2.【压缩版 Trie 树】

听教练 \(ysf\) 说有这么个东西,用来压空间效果比较好,\(\text{YudeS}\) 貌似学了,但我还不会。

3.【可持久化 Trie 树】

【模板】 最大异或和 \(\text{[P4735]}\)

这个应该是属于高阶数据结构吧

#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#define LL long long
#define Re register int
using namespace std;
const int N=6e5+5,logN=24;
int n,x,y,z,T,A[N],pt[N];char op;
inline void in(Re &x){
    Re fu=0;x=0;char ch=getchar();
    while(ch<'0'||ch>'9')fu|=ch=='-',ch=getchar();
    while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    x=fu?-x:x;
}
struct Trie{
    int O,cnt[N*(logN+2)],tr[N*(logN+2)][2];
    inline void insert(Re p,Re q,Re o,Re x){
        if(o<0)return;Re i=(x>>o)&1;
        tr[p][i]=++O,tr[p][i^1]=tr[q][i^1];
        cnt[tr[p][i]]=cnt[tr[q][i]]+1;
        insert(tr[p][i],tr[q][i],o-1,x);
    }
    inline int ask(Re p,Re q,Re o,Re x){
        if(o<0)return 0;Re i=(x>>o)&1;
        if(cnt[tr[q][i^1]]>cnt[tr[p][i^1]])return (1<<o)+ask(tr[p][i^1],tr[q][i^1],o-1,x);
        else return ask(tr[p][i],tr[q][i],o-1,x);
    }
}T1;
int main(){
//    freopen("123.txt","r",stdin);
    in(n),in(T);
    for(Re i=1;i<=n;++i)in(A[i]),A[i]^=A[i-1],T1.insert(pt[i]=++T1.O,pt[i-1],logN,A[i]);
    T1.insert(pt[0]=++T1.O,0,logN,0);
    while(T--){
        scanf(" %c",&op),in(x);
        if(op=='A')++n,A[n]=A[n-1]^x,T1.insert(pt[n]=++T1.O,pt[n-1],logN,A[n]);
        else in(y),in(z),--x,--y,printf("%d\n",T1.ask(x?pt[x-1]:0,pt[y],logN,z^A[n]));
    }
}

五:【KMP (Knuth-Morris-Pratt)】

1.【标准版 KMP】

【模板】 \(\text{KMP}\) 字符串匹配 \(\text{[P3375]}\)

#include<iostream>
#include<cstdio>
#include<string>
using namespace std;
int i,j,n,m,next[1000005];string a,b;
int main(){
    cin>>a>>b;
    n=a.size();a='$'+a;
    m=b.size();b='$'+b;
    for(i=2,j=0;i<=m;i++){
        while(j&&b[i]!=b[j+1])j=next[j];
        if(b[i]==b[j+1])j++;
        next[i]=j;
    }
    for(i=1,j=0;i<=n;i++){
        while(j&&a[i]!=b[j+1])j=next[j];
        if(a[i]==b[j+1])j++;
        if(j==m){j=next[j];printf("%d\n",i-m+1);}
    }
    for(i=1;i<=m;i++)printf("%d ",next[i]);
}

2.【扩展 KMP (EXKMP) / Z 函数】

【模板】 扩展 \(\text{KMP}\) \(\text{[P5410]}\)

还不会,先咕着。


六:【自动机与后缀三兄弟】

前方大量英文单词来袭,请做好准备Σ(⊙▽⊙"a)

1.【AC 自动机 (Aho-Corasick Automaton)】

【模板】 \(\text{AC}\) 自动机(二次加强版)\(\text{[P5357]}\)

#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
#define Re register int
#define LL long long
using namespace std;
const int N=2e5+3;
int n;char ch[N*10];
inline void in(Re &x){
    int fu=0;x=0;char c=getchar();
    while(c<'0'||c>'9')fu|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=fu?-x:x;
}
struct AC_Automaton{
    int O,edp[N],fail[N],tr[N][26];queue<int>Q;
    AC_Automaton(){O=1;}
    inline void insert(char ch[],Re id){
        Re p=1;
        for(Re i=1;ch[i];++i){
            Re a=ch[i]-'a';
            if(!tr[p][a])tr[p][a]=++O;
            p=tr[p][a];
        }
        edp[id]=p;
    }
    inline void get_fail(){
        for(Re i=0;i<26;++i)tr[0][i]=1;
        Q.push(1);
        while(!Q.empty()){
            Re x=Q.front();Q.pop();
            for(Re i=0;i<26;++i)
                if(tr[x][i])fail[tr[x][i]]=tr[fail[x]][i],Q.push(tr[x][i]);
                else tr[x][i]=tr[fail[x]][i];
        }
    }
    int ru[N],cnt[N];
    inline void sakura(char ch[]){
        for(Re i=1,p=1;ch[i];++i)p=tr[p][ch[i]-'a'],++cnt[p];
        for(Re i=2;i<=O;++i)++ru[fail[i]];
        for(Re i=1;i<=O;++i)if(!ru[i])Q.push(i);
        while(!Q.empty()){
            Re x=Q.front();Q.pop();
            cnt[fail[x]]+=cnt[x];
            if(!(--ru[fail[x]]))Q.push(fail[x]);
        }
        for(Re i=1;i<=n;++i)printf("%d\n",cnt[edp[i]]);
    }
}AC;
int main(){
//    freopen("123.txt","r",stdin);
    in(n);
    for(Re i=1;i<=n;++i)scanf("%s",ch+1),AC.insert(ch,i);
    AC.get_fail();
    scanf("%s",ch+1);AC.sakura(ch);
}

2.【回文自动机 / 回文树 / PAM (Palindromic Automaton)】

【模板】 回文自动机 \(\text{[P5496]}\)

#include<algorithm>
#include<cstring>
#include<cstdio>
#define Re register int
using namespace std;
const int N=5e5+5;
char s[N];
inline void in(Re &x){
    Re f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
struct Palindromic_Automaton{
    int O,last,siz[N],cnt[N],len[N],fail[N],trans[N][26];
    Palindromic_Automaton(){O=1,len[1]=-1,fail[0]=fail[1]=last=1;}
    //0:偶数串的根节点
    //1:奇数串的根节点
    //siz[x]:状态x出现的次数
    //cnt[x]:状态x所代表的回文串的回文后缀个数
    inline int find(Re p,Re id){while(s[id-1-len[p]]!=s[id])p=fail[p];return p;}
    inline void insert(Re ch,Re id){
        Re cur=find(last,id),now=trans[cur][ch];
        if(!now){
            now=++O,len[now]=len[cur]+2;
            fail[now]=trans[find(fail[cur],id)][ch];
            cnt[now]=cnt[fail[now]]+1;
            trans[cur][ch]=now;
        }
        ++siz[now],last=now;
    }
    inline void sakura(){
        for(Re i=O;i>=2;--i)siz[fail[i]]+=siz[i];
    }
}PAM;
int main(){
//    freopen("123.txt","r",stdin);
    scanf("%s",s+1);
    for(Re i=1,k=0;s[i];++i){
        s[i]=(s[i]-'a'+k)%26+'a';
        PAM.insert(s[i]-'a',i);
        printf("%d ",k=PAM.cnt[PAM.last]);
    }
}

3.【子序列自动机 】

【模板】 子序列自动机 \(\text{[P5826]}\)

还不会,先咕着。

4.【后缀数组 / SA (Suffix Array)】

(1).【倍增后缀数组算法 (Manber-Mayer Suffix Array)】

【模板】 后缀排序 \(\text{[P3809]}\)

#include<algorithm>
#include<cstring>
#include<cstdio>
#define Re register int
#define LL long long
using namespace std;
const int N=1e6+3;
inline void in(Re &x){
    int fu=0;x=0;char c=getchar();
    while(c<'0'||c>'9')fu|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=fu?-x:x;
}
struct Suffix_Array{
    int n,m,x[N],y[N],H[N],SA[N],cnt[N],rank[N],Height[N];char s[N];
    //Suf[i]: s[i,n]
    //len: 已经求出了长度为len的串的排名
    //x[i]: Suf[i]的排名 (排序第一关键字) (上一次排序后得到的rank[i])
    //y[i]: 按第len个字符排出来的顺序(排序第二关键字) (第二关键字排名为i的数的位置)
    //H[i]: H[i]=Height[rank[i]] 即 LCP(Suf[i],Suf[SA[rank[i]-1]])
    //SA[i]: 排名为i的后缀第一个字符所在位置
    //cnt[i]: 第一关键字排名为 i(1~i)的数有多少个
    //rank[i]: Suf[i]的排名
    //Height[i]: Height[i]=H[SA[i]] 即 LCP(Suf[SA[i]],Suf[SA[i-1]])
    inline void get_SA(){
        m=128;
        for(Re i=1;i<=n;++i)++cnt[x[i]=s[i]];
        for(Re i=2;i<=m;++i)cnt[i]+=cnt[i-1];
        for(Re i=n;i>=1;--i)SA[cnt[x[i]]--]=i;
        for(Re len=1;len<=n;len<<=1){
            Re t=0;
            for(Re i=n-len+1;i<=n;++i)y[++t]=i;
            for(Re i=1;i<=n;++i)if(SA[i]>len)y[++t]=SA[i]-len;
            
            for(Re i=1;i<=m;++i)cnt[i]=0;
            for(Re i=1;i<=n;++i)++cnt[x[i]];
            for(Re i=2;i<=m;++i)cnt[i]+=cnt[i-1];
            for(Re i=n;i>=1;--i)SA[cnt[x[y[i]]]--]=y[i],y[i]=0;
            
            swap(x,y);
            x[SA[1]]=m=1;//把旧的x存进了y并清零x
            for(Re i=2;i<=n;++i)
                x[SA[i]]=(y[SA[i]]==y[SA[i-1]]&&y[SA[i]+len]==y[SA[i-1]+len])?m:++m;//注意这里的SA[i]+len和SA[i-1]+len有可能访问到n+1,所以上面的N最好稍开大一点,不要尝试卡这点小常数空间
            if(m==n)break;
        }
        for(Re i=1;i<=n;++i)printf("%d ",SA[i]);
    }
    inline void get_Height(){
        for(Re i=1;i<=n;++i)rank[SA[i]]=i;
        for(Re i=1,k=0;i<=n;++i){
            if(rank[i]==1)continue;
            if(k)--k;//H[i-1]-1<=H[i]
            Re j=SA[rank[i]-1];
            while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k])++k;
            Height[rank[i]]=k;
        }
//        for(Re i=1;i<=n;++i)printf("%d ",Height[i]);
    }
}SA;
int main(){
//    freopen("123.txt","r",stdin);
    scanf("%s",SA.s+1),SA.n=strlen(SA.s+1);
    SA.get_SA(),SA.get_Height();
}

(2).【SA-IS (Suffix-Array-Induce-Sort)】

还不会,先咕着。

(3).【DC3 (Difference Cover modulo 3)】

还不会,先咕着。

(4).【动态后缀数组 / 后缀平衡树】

【模板】 后缀平衡树 \(\text{[P6164]}\)

还不会,先咕着。

(5).【SAM 转后缀树转后缀数组】

【模板】 不同子串个数 \(\text{[P2408]}\)

#include<algorithm>
#include<cstring>
#include<cstdio>
#define LL long long
#define Re register int
using namespace std;
const int N=1e5+5;
int n;char s[N];LL ans;
inline void in(Re &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
struct Suffix_Array{
    int H[N],SA[N],rank[N],height[N];
    inline void get_height(){
        for(Re i=1;i<=n;++i)rank[SA[i]]=i;
        for(Re i=1,k=0;i<=n;++i){
            if(rank[i]==1)continue;
            if(k)--k;
            Re j=SA[rank[i]-1];
            while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k])++k;
            height[rank[i]]=H[i]=k;
        }
    }
}SA;
int pan[N<<1],pos[N<<1];
struct Suffix_Tree{
    int t,tr[N<<1][26];
    inline void add(Re x,Re y,Re c){tr[x][c]=y;}
    inline void dfs(Re x){
        if(pan[x])SA.SA[++t]=pos[x];
        for(Re i=0;i<26;++i)if(tr[x][i])dfs(tr[x][i]);
    }
}ST;
struct Suffix_Automaton{
    int O,last,link[N<<1],maxlen[N<<1],trans[N<<1][26];
    Suffix_Automaton(){last=O=1;}
    inline void insert(Re ch,Re id){
        Re z=++O,p=last;maxlen[z]=maxlen[p]+1,pan[z]=1,pos[z]=id;
        while(p&&!trans[p][ch])trans[p][ch]=z,p=link[p];
        if(!p)link[z]=1;
        else{
            Re x=trans[p][ch];
            if(maxlen[p]+1==maxlen[x])link[z]=x;
            else{
                Re y=++O;maxlen[y]=maxlen[p]+1;
                for(Re i=0;i<26;++i)trans[y][i]=trans[x][i];
                while(p&&trans[p][ch]==x)trans[p][ch]=y,p=link[p];
                pos[y]=pos[x],link[y]=link[x],link[x]=link[z]=y;//注意pos[y]=pos[x]
            }
        }
        last=z;
    }
    inline void build(){
        for(Re i=n;i>=1;--i)insert(s[i]-'a',i);
        for(Re i=2;i<=O;++i)ST.add(link[i],i,s[pos[i]+maxlen[link[i]]]-'a');//转移边上的ch为子节点的首字母
    }
}SAM;
int main(){
//    freopen("123.txt","r",stdin);
    in(n),scanf("%s",s+1),SAM.build(),ST.dfs(1),SA.get_height();
    for(Re i=1;i<=n;++i)ans+=n-SA.SA[i]+1-SA.height[i];
    printf("%lld\n",ans);
}

5.【后缀自动机 / SAM (Suffix Automaton)】

(1).【标准版 SAM】

【模板】 后缀自动机(\(\text{SAM}\)\(\text{[P3804]}\)

#include<algorithm>
#include<cstdio>
#include<queue>
#define Re register int
#define LL long long
using namespace std;
const int N=2e6+5;
char ch[2000003];
inline void in(Re &x){
    int fu=0;x=0;char c=getchar();
    while(c<'0'||c>'9')fu|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=fu?-x:x;
}
struct Suffix_Automaton{
    int O,last,ru[N],siz[N],link[N],maxlen[N],minlen[N],trans[N][26];queue<int>Q;
    //siz:一个等价类节点endpos大小(即出现次数) 
    Suffix_Automaton(){last=O=1;}//起点为 1 
    inline void insert(char ch){
        Re z=++O,p=last;siz[z]=1,maxlen[z]=maxlen[last]+1;//len为主链长
        while(p&&!trans[p][ch])trans[p][ch]=z,p=link[p];//没有trans[...][ch]的一段
        if(!p)link[z]=1;//没找到trans[...][ch]
        else{
            Re x=trans[p][ch];
            if(maxlen[p]+1==maxlen[x])link[z]=x;//直接接上去 
            else{//拆点 
                Re y=++O;maxlen[y]=maxlen[p]+1;//把原x拆成小于等于maxlen[p]+1的一段(即y)和大于maxlen[p]+1的一段(即新x)
                
                for(Re i=0;i<26;++i)trans[y][i]=trans[x][i];//复制原x的trans 
                while(p&&trans[p][ch]==x)trans[p][ch]=y,p=link[p];//把指向了原x的那一段都指向y 
                link[y]=link[x];//复制原x的link
                
                link[z]=link[x]=y;
            }
        }
        last=z;
    }
    inline void Tuopu(){//在正向link构成的DAG上跑拓扑求siz 
        for(Re i=2;i<=O;++i)++ru[link[i]];
        for(Re i=1;i<=O;++i)if(!ru[i])Q.push(i);
        while(!Q.empty()){
            Re x=Q.front();Q.pop();
            siz[link[x]]+=siz[x];
            if(!(--ru[link[x]]))Q.push(link[x]);
        }
    }
    inline void sakura(){
        for(Re i=2;i<=O;++i)minlen[i]=maxlen[link[i]]+1;
        Tuopu();Re ans=0;
        for(Re i=1;i<=O;++i)if(siz[i]>1)ans=max(ans,siz[i]*maxlen[i]);
        printf("%d\n",ans);
    }
}SAM;
int main(){
//    freopen("123.txt","r",stdin);
    scanf("%s",ch+1);
    for(Re i=1;ch[i];++i)SAM.insert(ch[i]-'a');
    SAM.sakura();
}

(2).【LCT 动态维护 SAM】

【模板】 \(\text{SubString [Bzoj2555]}\)

#include<algorithm>
#include<cstring>
#include<cstdio>
#include<string>
#include<queue>
#define LL long long
#define Re register int
using namespace std;
const int N=12e5+3;
int T,ans,mask;char op[10],s[N];
inline void in(Re &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
inline string In(Re mask){
    scanf("%s",s);string chars=s;
    for(int j=0;j<chars.length();j++){
        mask=(mask*131+j)%chars.length();
        char t=chars[j];
        chars[j]=chars[mask];
        chars[mask]=t;
    }
    return chars;
}
struct Suffix_Automaton{
    struct Link_Cut_Tree{
        #define pl tr[p].ps[0]
        #define pr tr[p].ps[1]
        #define pf tr[p].fa
        int Q[N];
        struct QAQ{int fa,ans,add,ps[2];}tr[N];
        inline void add(Re p,Re v){tr[p].ans+=v,tr[p].add+=v;}
        inline void pushdown(Re p){
            Re v=tr[p].add;
            if(v){
                if(pl)add(pl,v);
                if(pr)add(pr,v);
                tr[p].add=0;
            }
        }
        inline int nort(Re p){return tr[pf].ps[0]==p||tr[pf].ps[1]==p;}
        inline int which(Re p){return tr[pf].ps[1]==p;}
        inline void connect(Re p,Re fa,Re o){tr[pf=fa].ps[o]=p;}
        inline void rotate(Re p){
            Re fa=pf,fas=which(p);
            Re pa=tr[fa].fa,pas=which(fa);
            Re x=tr[p].ps[fas^1];
            if(nort(fa))tr[pa].ps[pas]=p;pf=pa;
            connect(x,fa,fas),connect(fa,p,fas^1);
        }
        inline void splay(Re p){
            Re x=p,t=0;Q[++t]=x;
            while(nort(x))Q[++t]=x=tr[x].fa;
            while(t)pushdown(Q[t--]);
            for(Re fa;nort(p);rotate(p))
                if(nort(fa=pf))rotate(which(p)==which(fa)?fa:p);
        }
        inline void access(Re p){for(Re son=0;p;son=p,p=pf)splay(p),pr=son;}
        inline void link(Re x,Re y){tr[x].fa=y,access(y),splay(y),add(y,tr[x].ans);}
        inline void cut(Re x){
            access(x),splay(x),add(tr[x].ps[0],-tr[x].ans);
            tr[tr[x].ps[0]].fa=0,tr[x].ps[0]=0;
        }
        inline int ask(Re x){splay(x);return tr[x].ans;}
    }LCT;
    int O,last,link[N],maxlen[N],trans[N][30];
    Suffix_Automaton(){last=O=1;}
    inline void insert(char ch){
        Re z=++O,p=last;LCT.tr[z].ans=1,maxlen[z]=maxlen[last]+1;
        while(p&&!trans[p][ch])trans[p][ch]=z,p=link[p];
        if(!p)link[z]=1,LCT.link(z,1);
        else{
            Re x=trans[p][ch];
            if(maxlen[x]==maxlen[p]+1)link[z]=x,LCT.link(z,x);
            else{
                Re y=++O;maxlen[y]=maxlen[p]+1;
                for(Re i=0;i<26;++i)trans[y][i]=trans[x][i];
                while(p&&trans[p][ch]==x)trans[p][ch]=y,p=link[p];
                link[y]=link[x],LCT.cut(x),LCT.link(y,link[y]);
                link[x]=link[z]=y,LCT.link(x,y),LCT.link(z,y);
            }
        }
        last=z;
    }
    inline void add(string ch){
        for(Re i=0;ch[i];++i)insert(ch[i]-'A');
    }
    inline int ask(string ch){
        Re p=1;
        for(Re i=0;ch[i];++i)if(!(p=trans[p][ch[i]-'A']))return 0;
        return LCT.ask(p);
    }
}SAM;
int main(){
//    freopen("123.txt","r",stdin);
    in(T),scanf("%s",s+1);
    for(Re i=1;s[i];++i)SAM.insert(s[i]-'A');
    while(T--){
        scanf("%s",op);string s=In(mask);
        if(op[0]=='A')SAM.add(s);
        else mask^=(ans=SAM.ask(s)),printf("%d\n",ans);
    }
}

(3).【广义 SAM(在线构造)】

【模板】 广义后缀自动机(广义 \(\text{SAM}\)\(\text{[P6139]}\)

#include<algorithm>
#include<cstdio>
#include<queue>
#define Re register int
#define LL long long
using namespace std;
const int N=2e6+5;
int n;char ch[N];
inline void in(Re &x){
    int fu=0;x=0;char c=getchar();
    while(c<'0'||c>'9')fu|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=fu?-x:x;
}
struct Suffix_Automaton{
    int O,link[N],maxlen[N],trans[N][26];
    //link[i]: 后缀链接
    //trans[i]: 状态转移数组
    Suffix_Automaton(){O=1;}//根初始化为1
    inline int insert(Re ch,Re last){
        if(trans[last][ch]){
            Re p=last,x=trans[p][ch];
            if(maxlen[p]+1==maxlen[x])return x;//即最初的特判1
            else{
                Re y=++O;maxlen[y]=maxlen[p]+1;
                for(Re i=0;i<26;++i)trans[y][i]=trans[x][i];
                while(p&&trans[p][ch]==x)trans[p][ch]=y,p=link[p];
                link[y]=link[x],link[x]=y;
                return y;//即最初的特判2
            }
        }
        Re z=++O,p=last;maxlen[z]=maxlen[last]+1;
        while(p&&!trans[p][ch])trans[p][ch]=z,p=link[p];
        if(!p)link[z]=1;
        else{
            Re x=trans[p][ch];
            if(maxlen[p]+1==maxlen[x])link[z]=x;
            else{
                Re y=++O;maxlen[y]=maxlen[p]+1;
                for(Re i=0;i<26;++i)trans[y][i]=trans[x][i];
                while(p&&trans[p][ch]==x)trans[p][ch]=y,p=link[p];
                link[y]=link[x],link[z]=link[x]=y;
            }
        }
        return z;
    }
    inline void sakura(){
        LL ans=0;
        for(Re i=2;i<=O;++i)ans+=maxlen[i]-maxlen[link[i]];
        printf("%lld\n",ans);
    }
}SAM;
int main(){
//    freopen("123.txt","r",stdin);
    in(n);
    for(Re i=1;i<=n;++i){
        scanf("%s",ch+1);Re last=1;
        for(Re j=1;ch[j];++j)last=SAM.insert(ch[j]-'a',last);
    }
    SAM.sakura();
}

(4).【广义 SAM(离线构造)】

【模板】 广义后缀自动机(广义 \(\text{SAM}\)\(\text{[P6139]}\)

#include<algorithm>
#include<cstdio>
#include<queue>
#define Re register int
#define LL long long
using namespace std;
const int N=2e6+5,M=1e6+3;
int n,t;char ch[N];
inline void in(Re &x){
    int fu=0;x=0;char c=getchar();
    while(c<'0'||c>'9')fu|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=fu?-x:x;
}
struct Trie{
    int O,c[M],fa[M],tr[M][26];
    //fa[x]: Trie树上x的父节点
    //c[x]: Trie树上x的颜色
    Trie(){O=1;}//根初始化为1
    inline void insert(char ch[]){
        Re p=1;
        for(Re i=1;ch[i];++i){
            Re a=ch[i]-'a';
            if(!tr[p][a])tr[p][a]=++O,fa[O]=p,c[O]=a;
            p=tr[p][a];
        }
    }
}T1;
struct Suffix_Automaton{
    int O,pos[N],link[N],maxlen[N],trans[N][26];queue<int>Q;
    //pos[x]:Trie上的x节点(路径1->x所表示的字符串)在SAM上的对应节点编号
    //link[i]: 后缀链接
    //trans[i]: 状态转移数组
    Suffix_Automaton(){O=1;}//根初始化为1
    inline int insert(Re ch,Re last){//和普通SAM一样
        Re x,y,z=++O,p=last;maxlen[z]=maxlen[last]+1;
        while(p&&!trans[p][ch])trans[p][ch]=z,p=link[p];
        if(!p)link[z]=1;
        else{
            x=trans[p][ch];
            if(maxlen[p]+1==maxlen[x])link[z]=x;
            else{
                y=++O;maxlen[y]=maxlen[p]+1;
                for(Re i=0;i<26;++i)trans[y][i]=trans[x][i];
                while(p&&trans[p][ch]==x)trans[p][ch]=y,p=link[p];
                link[y]=link[x],link[z]=link[x]=y;
            }
        }
        return z;
    }
    inline void build(){//bfs遍历Trie树构造广义SAM
        for(Re i=0;i<26;++i)if(T1.tr[1][i])Q.push(T1.tr[1][i]);//插入第一层字符
        pos[1]=1;//Tire树上的根1在SAM上的位置为根1
        while(!Q.empty()){
            Re x=Q.front();Q.pop();
            pos[x]=insert(T1.c[x],pos[T1.fa[x]]);//注意是pos[Trie->fa[x]]
            for(Re i=0;i<26;++i)if(T1.tr[x][i])Q.push(T1.tr[x][i]);
        }
    }
    inline void sakura(){
        LL ans=0;
        for(Re i=2;i<=O;++i)ans+=maxlen[i]-maxlen[link[i]];
        printf("%lld\n",ans);
    }
}SAM;
int main(){
//    freopen("123.txt","r",stdin);
    in(n);
    for(Re i=1;i<=n;++i)scanf("%s",ch+1),T1.insert(ch);
    SAM.build(),SAM.sakura();
}

(5).【线段树合并维护广义 SAM 的 siz(等价类出现次数)】

【模板】 \(\text{Forensic Examination}\) \(\text{[CF666E]}\)

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
#define LL long long
#define Re register int
using namespace std;
const int N=5e5+3,M=5e4+3,logN=21;
int n,m,x,y,l,r,T,pos[N];char s[N],ch[M];
inline void in(Re &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
struct QWQ{
    int x,id;QWQ(Re X=0,Re ID=0){x=X,id=ID;}
    inline bool operator>(const QWQ &O)const{return x!=O.x?x>O.x:id<O.id;}
};
inline QWQ max(QWQ A,QWQ B){return A>B?A:B;}
int pt[N+M<<1];
struct Segment_Tree{
    #define pl (tr[p].lp)
    #define pr (tr[p].rp)
    #define mid ((L+R)>>1)
    int O;
    struct QAQ{int lp,rp;QWQ ans;}tr[(M<<1)*30];
    inline void pushup(Re p){
        tr[p].ans=max(tr[pl].ans,tr[pr].ans);
    }
    inline void change(Re &p,Re L,Re R,Re x){
        if(!p)p=++O;
        if(L==R){++tr[p].ans.x,tr[p].ans.id=L;return;}
        if(x<=mid)change(pl,L,mid,x);
        else change(pr,mid+1,R,x);
        pushup(p);
    }
    inline int merge(Re p,Re q,Re L,Re R){
        if(!p||!q)return p+q;
        Re x=++O;
        if(L==R){tr[x]=tr[p],tr[x].ans.x+=tr[q].ans.x;return x;}
        tr[x].lp=merge(pl,tr[q].lp,L,mid);
        tr[x].rp=merge(pr,tr[q].rp,mid+1,R);
        pushup(x);return x;
    }
    inline QWQ ask(Re p,Re L,Re R,Re l,Re r){
        if(!p)return QWQ(0,m+1);
        if(l<=L&&R<=r)return tr[p].ans;
        QWQ ans=QWQ(0,m+1);
        if(l<=mid)ans=max(ans,ask(pl,L,mid,l,r));
        if(r>mid)ans=max(ans,ask(pr,mid+1,R,l,r));
        return ans;
    }
}TR;
struct Suffix_Automaton{
    int O,link[N+M<<1],maxlen[N+M<<1],trans[N+M<<1][26];
    Suffix_Automaton(){O=1;}
    inline int insert(Re ch,Re last,Re id){
        if(trans[last][ch]){
            Re p=last,x=trans[p][ch];
            if(maxlen[p]+1==maxlen[x]){if(id)TR.change(pt[x],1,m,id);return x;}
            else{
                Re y=++O;maxlen[y]=maxlen[p]+1;
                for(Re i=0;i<26;++i)trans[y][i]=trans[x][i];
                while(p&&trans[p][ch]==x)trans[p][ch]=y,p=link[p];
                link[y]=link[x],link[x]=y;
                if(id)TR.change(pt[y],1,m,id);
                return y;
            }
        }
        Re z=++O,p=last;maxlen[z]=maxlen[p]+1;
        while(p&&!trans[p][ch])trans[p][ch]=z,p=link[p];
        if(!p)link[z]=1;
        else{
            Re x=trans[p][ch];
            if(maxlen[x]==maxlen[p]+1)link[z]=x;
            else{
                Re y=++O;maxlen[y]=maxlen[p]+1;
                for(Re i=0;i<26;++i)trans[y][i]=trans[x][i];
                while(p&&trans[p][ch]==x)trans[p][ch]=y,p=link[p];
                link[y]=link[x],link[x]=link[z]=y;
            }
        }
        if(id)TR.change(pt[z],1,m,id);
        return z;
    }
    int o,deep[N+M<<1],head[N+M<<1],ant[N+M<<1][23];
    struct QAQ{int to,next;}a[N+M<<1];
    inline void add(Re x,Re y){a[++o].to=y,a[o].next=head[x],head[x]=o;}
    inline void dfs(Re x,Re fa){
        deep[x]=deep[ant[x][0]=fa]+1;
        for(Re i=1;(1<<i)<=deep[x];++i)ant[x][i]=ant[ant[x][i-1]][i-1];
        for(Re i=head[x],to;i;i=a[i].next)
            dfs(to=a[i].to,x),pt[x]=TR.merge(pt[x],pt[to],1,m);
    }
    inline void build(){
        for(Re i=2;i<=O;++i)add(link[i],i);dfs(1,0);
    }
    inline int get(Re x,Re len){
        Re p=pos[x];
        for(Re i=logN;i>=0;--i)if(ant[p][i]&&maxlen[ant[p][i]]>=len)p=ant[p][i];
        return p;
    }
    inline void sakura(Re l,Re r,Re x,Re y){
        QWQ ans=TR.ask(pt[get(y,y-x+1)],1,m,l,r);
        if(ans.x==0)ans.id=l;
        printf("%d %d\n",ans.id,ans.x);
    }
}SAM;
int main(){
//    freopen("123.txt","r",stdin);
    scanf("%s",s+1),n=strlen(s+1),in(m);
    for(Re i=1;i<=m;++i){
        scanf("%s",ch+1);Re last=1;
        for(Re j=1;ch[j];++j)last=SAM.insert(ch[j]-'a',last,i);
    }
    for(Re i=1,last=1;i<=n;++i)pos[i]=last=SAM.insert(s[i]-'a',last,0);
    SAM.build(),in(T);
    while(T--)in(l),in(r),in(x),in(y),SAM.sakura(l,r,x,y);
}

6.【后缀树 / ST (Suffix Tree)】

(1).【Ukk算法 (Ukkonen)】

(2).【SAM 转后缀树】

【题解】字母 \(\text{(letter)}\)

#include<algorithm>
#include<cstring>
#include<cstdio>
#define LL long long
#define Re register int
using namespace std;
const int N=4e5+10;
int n,x,y,T;LL P,M,G,K;char s[N>>1];
inline void in(Re &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
struct Sakura2{
    int O,last,pos[N],siz[N],link[N],maxlen[N],trans[N][26];
    //siz[x]: 节点x的出现次数 (endpos的大小)
    //link[x]: x在parent树上的父亲
    //pos[x]: 节点x某一次出现位置 (用于构建后缀树中的压缩边link[x]->x),
    ///翻转后的s中 倒序遍历pos[x]-maxlen[link[x]] -> pos[x]-maxlen[x]+1得到的字符串 即为后缀树中的压缩边 link[x]->x
    inline void insert(Re ch,Re id){//SAM新建节点
        Re z=++O,p=last;pos[z]=id,siz[z]=1,maxlen[z]=maxlen[last]+1;
        while(p&&!trans[p][ch])trans[p][ch]=z,p=link[p];
        if(!p)link[z]=1;
        else{
            Re x=trans[p][ch];
            if(maxlen[p]+1==maxlen[x])link[z]=x;
            else{
                Re y=++O;maxlen[y]=maxlen[p]+1,pos[y]=pos[x];
                for(Re i=0;i<26;++i)trans[y][i]=trans[x][i];
                while(p&&trans[p][ch]==x)trans[p][ch]=y,p=link[p];
                link[y]=link[x],link[z]=link[x]=y;
            }
        }
        last=z;
    }
    int t,ID[N],to[N][26];LL S[N];
    //to[x][ch]: 后缀树的trans数组
    //ID[x]: 顺序遍历后缀树的第x个节点编号
    //S[x]: 顺序遍历后缀树的前x个节点总共代表了多少个字符
    inline void dfs1(Re x){//遍历 SAM的parent树\后缀树 获取siz
        for(Re i=0;i<26;++i)
            if(to[x][i])dfs1(to[x][i]),siz[x]+=siz[to[x][i]];
    }
    inline void dfs2(Re x){//遍历 SAM的parent树\后缀树 获取节点顺序
        if(x!=1)ID[++t]=x;//没有储存信息的根节点1就不要了
        for(Re i=0;i<26;++i)if(to[x][i])dfs2(to[x][i]);
    }
    inline LL calc(Re L,Re R){return 1ll*(L+R)*(R-L+1)/2;}//计算从L加到R的等差数列 
    inline void build(){
        last=O=1;//根节点设为1
        for(Re i=1;i<=n/2;++i)swap(s[i],s[n-i+1]);//翻转原字符串
        for(Re i=1;i<=n;++i)insert(s[i]-'a',i);
        for(Re i=2;i<=O;++i)to[link[i]][s[pos[i]-maxlen[link[i]]]-'a']=i;//构建后缀树。
        //由于pos[x]是endpos[x]中的任意一个,所以获取边link[x]->x压缩掉的字符串信息时只能用pos[x],不能用pos[link[x]]
        dfs1(1),dfs2(1);//遍历 SAM的parent树\后缀树
        for(Re i=1;i<=t;++i)x=ID[i],S[i]=S[i-1]+1ll*siz[x]*calc(maxlen[link[x]]+1,maxlen[x]);
    }
    inline char ask(LL K){
        Re l=1,r=t,x;
        while(l<r){//二分找到第一个大于等于K的位置
            Re mid=l+r>>1;
            if(S[mid]<K)l=mid+1;
            else r=mid;
        }
        x=ID[l],K-=S[l-1],l=maxlen[link[x]]+1,r=maxlen[x];
        while(l<r){//二分找到第一个大于等于K的位置
            Re mid=l+r>>1;
            if(1ll*siz[x]*calc(maxlen[link[x]]+1,mid)<K)l=mid+1;
            else r=mid;
        }
        K-=1ll*siz[x]*calc(maxlen[link[x]]+1,l-1),K=(K-1)%l+1;//注意取模的方式
        return s[pos[x]-K+1];//注意方向:往左数第K位
    }
    char ans;
    inline void sakura(){
        build();
        while(T--)scanf("%lld%lld",&P,&M),K=P*G%M+1,G+=(ans=ask(K)),putchar(ans),puts("");
    }
}T2;
int main(){
    freopen("letter.in","r",stdin);
    freopen("letter.out","w",stdout);
    scanf("%s",s+1),n=strlen(s+1);in(T);
    T2.sakura();
    fclose(stdin);
    fclose(stdout);
}
posted @ 2019-10-28 21:47  辰星凌  阅读(773)  评论(0编辑  收藏  举报