• 字符串做题笔记


    AC自动机:

    P5357 【模板】AC自动机(二次加强版)

    ·不要像以前一样习惯性把trie树的根设为1,从0开始的话后面getfail比较方便。

    ·trie树的节点编号是无序的,统计答案需要dfs或者拓扑,按编号循环显然是错的。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<queue>
    using namespace std;
    const int S=2e6+10,N=2e5+10;
    int n,cnt[N],now,tot,len,tree[N][27],ed[N],num[N],fail[N];
    char s[S];
    int head[N],Next[N],tot1,ver[N],du[N];
    void add(int x,int y){
        ver[++tot1]=y;
        Next[tot1]=head[x];
        head[x]=tot1;
    } 
    void insert(int x){
        now=0;
        len=strlen(s+1);
        for(int i=1;i<=len;i++){
            if(!tree[now][s[i]-'a'])tree[now][s[i]-'a']=++tot;
            now=tree[now][s[i]-'a'];
        }
        ed[now]=1;
        cnt[x]=now;
    }
    queue<int>q;
    void getfail(){
        for(int i=0;i<26;i++){
            if(tree[0][i]){
                fail[tree[0][i]]=0;
                q.push(tree[0][i]);
                add(tree[0][i],0);
                du[0]++;
            }
        }
        while(q.size()){
            int u=q.front();
            q.pop();
            for(int i=0;i<26;i++){
                int v=tree[u][i];
                if(v){
                    fail[v]=tree[fail[u]][i];
                    q.push(v);
                    add(v,fail[v]);
                    du[fail[v]]++;
                }
                else{
                    tree[u][i]=tree[fail[u]][i];
                }
            }
        }
    }
    void AC(){
        now=0;
        len=strlen(s+1);
        for(int i=1;i<=len;i++){
            int v=tree[now][s[i]-'a'];
            num[v]++;
            now=v;
        }
    }
    int main()
    {
    //    freopen("1.in","r",stdin);
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            scanf("%s",s+1);
            insert(i);
        }
        getfail();
        scanf("%s",s+1);
        AC();
        for(int i=1;i<=tot;i++){
            if(!du[i])q.push(i);
        }
        while(q.size()){
            int u=q.front();
            q.pop();
            for(int i=head[u];i;i=Next[i]){
                int y=ver[i];
                num[y]+=num[u];
                du[y]--;
                if(!du[y])q.push(y);
            }
        }
        for(int i=1;i<=n;i++){
            printf("%d
    ",num[cnt[i]]);
        }
        return 0;
    }
    /*
    6
    a
    bb
    aa
    abaa
    abaaa
    abaaa
    abaaabaa
    */
    P5357 【模板】AC自动机(二次加强版)

    P2444 [POI2000]病毒

    ·跳fail能跳到危险节点的节点,自身必为危险节点。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<queue>
    using namespace std;
    int n,now,tot,len,flag;
    char s[30010];
    int tree[30010][2],ed[30010],fail[30010];
    void insert(){
        now=0;
        len=strlen(s+1);
        for(int i=1;i<=len;i++){
            if(!tree[now][s[i]-'0'])tree[now][s[i]-'0']=++tot;
            now=tree[now][s[i]-'0'];
        }
        ed[now]=1;
    }
    queue<int>q;
    void getfail(){
        for(int i=0;i<2;i++){
            fail[tree[0][i]]=0;
            if(tree[0][i]){    
                q.push(tree[0][i]);
            }
        }
        while(!q.empty()){
            int u=q.front();
            q.pop();
            for(int i=0;i<2;i++){
                int v=tree[u][i];
                if(v){
                    fail[v]=tree[fail[u]][i];
                    if(ed[fail[v]])ed[v]=1;
                    q.push(v);
                }
                else tree[u][i]=tree[fail[u]][i];
            }
        }
    }
    int vis[30010];
    int dfs(int x){
        if(flag)return 1;
        if(ed[x])return 0;
        if(vis[x])return 1;
        if(x)vis[x]=1;
        int val=(dfs(tree[x][0])|dfs(tree[x][1]));
        if(val)flag=1;
        vis[x]=0;
        return val;
    }
    int main()
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            scanf("%s",s+1);
            insert();
        }
        getfail();
        if(dfs(0))printf("TAK
    ");
        else printf("NIE
    ");
        return 0;
    }
    /*
    3
    011
    11
    00000
    */
    P2444 [POI2000]病毒

    P4052 [JSOI2007]文本生成器

    ·trie树里很多走不到的字母相当于回到根,转移和统计带根一起算。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<queue>
    using namespace std;
    int n,m;
    char s[110];
    int now,len,tot,tree[6010][26],fail[6010],ed[6010];
    void insert(){
        now=0;
        len=strlen(s+1);
        for(int i=1;i<=len;i++){
            if(!tree[now][s[i]-'A'])tree[now][s[i]-'A']=++tot;
            now=tree[now][s[i]-'A'];
        }
        ed[now]=1;
    }
    queue<int>q;
    void getfail(){
        for(int i=0;i<26;i++){
            if(tree[0][i]){
                q.push(tree[0][i]);
            }
        }
        while(q.size()){
            int u=q.front();
            q.pop();
            for(int i=0;i<26;i++){
                int v=tree[u][i];
                if(v){
                    fail[v]=tree[fail[u]][i];
                    q.push(v);
                    if(ed[fail[v]])ed[v]=1;
                }
                else{
                    tree[u][i]=tree[fail[u]][i];
                }
            }
        }
    }
    int f[110][6010],ans;
    void work(){
        f[0][0]=1;
        for(int t=0;t<m;t++){
            for(int i=0;i<=tot;i++){
                if(ed[i])continue;
                for(int j=0;j<26;j++){
                    if(ed[tree[i][j]])continue;
                    f[t+1][tree[i][j]]=(f[t+1][tree[i][j]]+f[t][i])%10007;
                }
            }
        }
        for(int i=0;i<=tot;i++)ans=(ans+f[m][i])%10007;
    }
    int ks(int x,int k){
        int num=1;
        while(k){
            if(k&1)num=num*x%10007;
            x=x*x%10007;
            k>>=1;
        }
        return num;
    }
    int main()
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++){
            scanf("%s",s+1);
            insert();
        }
        getfail();
        work();
        printf("%d
    ",((ks(26,m)-ans)%10007+10007)%10007);
        return 0;
    }
    P4052 [JSOI2007]文本生成器

    P2414 [NOI2011]阿狸的打字机

     ·这题具体写个题解吧

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<vector>
    #include<queue>
    using namespace std;
    const int N=1e5+10;
    char s[N];
    int fa[N],lens,now,tree[N][26],tree1[N][26],num,ed[N],tot,m,fail[N],ans[N];
    struct node{
        int x,id;
        node(int a=0,int b=0){
            x=a,id=b;
        }
    };
    vector<node>v[N];
    vector<int>e[N];
    void insert(){
        for(int j=1;j<=lens;j++){
            if(s[j]=='B')now=fa[now];
            else if(s[j]=='P'){
                num++;
                ed[num]=now;
                e[now].push_back(num);
            }
            else{
                if(!tree[now][s[j]-'a'])tree1[now][s[j]-'a']=tree[now][s[j]-'a']=++tot,fa[tot]=now;
                now=tree[now][s[j]-'a'];
            }
        }
    }
    int Next[N],head[N],tot1,ver[N];
    void add(int x,int y){
        ver[++tot1]=y;
        Next[tot1]=head[x];
        head[x]=tot1;
    }
    queue<int>q0;
    void getfail(){
        for(int i=0;i<26;i++){
            if(tree[0][i]){
                add(0,tree[0][i]);
                q0.push(tree[0][i]);
            }
        }
        while(q0.size()){
            int u=q0.front();
            q0.pop();
            for(int i=0;i<26;i++){
                int v=tree1[u][i];
                if(v){
                    fail[v]=tree1[fail[u]][i];
                    add(fail[v],v);
                    q0.push(v);
                }
                else tree1[u][i]=tree1[fail[u]][i];
            }
        }
    }
    int tim,rec[N],rec1[N];
    void dfs(int x){
        rec[x]=++tim;
        for(int i=head[x];i;i=Next[i]){
            int y=ver[i];
            dfs(y);
        }
        rec1[x]=tim;
    }
    int tr[N];
    void add1(int x,int val){
        for(;x<=tim;x+=(x&-x))tr[x]+=val;
    }
    int ask(int x){
        int sum=0;
        for(;x;x-=(x&-x))sum+=tr[x];
        return sum;
    }
    void solve(int x){
        for(int i=0;i<v[x].size();i++){
            node y=v[x][i];
            ans[y.id]=ask(rec1[ed[y.x]])-ask(rec[ed[y.x]]-1);
        }
    }
    void dfs1(int x){
        if(e[x].size()){
            for(int i=0;i<e[x].size();i++){
                int y=e[x][i];
                solve(y);
            }
        }
        for(int i=0;i<26;i++){
            if(tree[x][i]){
                add1(rec[tree[x][i]],1);
                dfs1(tree[x][i]);
                add1(rec[tree[x][i]],-1);
            }
        }
    }
    int main()
    {
        scanf("%s",s+1);
        lens=strlen(s+1);
        insert();
        getfail();
        dfs(0);
        scanf("%d",&m);
        for(int i=1,x,y;i<=m;i++){
            scanf("%d%d",&x,&y);
            v[y].push_back(node(x,i));
        }
        dfs1(0);
        for(int i=1;i<=m;i++)printf("%d
    ",ans[i]);
        return 0;
     } 
    P2414 [NOI2011]阿狸的打字机

    回文自动机:

    (manacher)P3805 【模板】manacher算法

    ·注意细节。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    char s[23000010],a[11000010];
    int len,lst=0,p[23000010],pos,ans;
    void manacher(){
        for(int i=1;i<=len*2+1;i++){
            if(lst<i){
                pos=i;
                for(int j=i;j<=len*2+1&&2*i-j>0;j++){
                    if(s[j]!=s[2*i-j])break;
                    p[i]++;
                    lst=j;
                }
            }
            else{
                int v=2*pos-i;
                if(i+p[v]-1>lst)p[i]=lst-i+1;
                else if(i+p[v]-1<lst)p[i]=p[v];
                else{
                    p[i]=p[v];
                    while(s[i+p[i]]==s[i-p[i]])p[i]++;
                    lst=i+p[i]-1;
                    pos=i;
                }
            }
        }
    }
    int main()
    {
        scanf("%s",a+1);
        len=strlen(a+1);
        s[0]=s[1]='#';
        for(int i=1;i<=len;i++){
            s[i*2]=a[i];
            s[i*2+1]='#';
        }
        manacher();
        for(int i=1;i<=2*len+1;i++)ans=max(ans,p[i]-1);
        printf("%d
    ",ans);
        return 0;
    }
    P3805 【模板】manacher算法

    (manacher)P4555 [国家集训队]最长双回文串

    ·注意细节,题目要求两回文串合起来才满足条件,那么边界就是不满足的。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    const int N=2e5+10;
    char s[N],s0[N];
    int p[N],lens,l[N],r[N],ans;
    void manacher()
    {
        p[0]=1;
        int pos=0,right=0;
        for(int i=1;i<=lens;i++){
            if(right<i){
                p[i]=1;
                while(s[i-p[i]]==s[i+p[i]]&&i-p[i]>=0&&i+p[i]<=lens)p[i]++;
                right=(i+p[i]-1);
                pos=i;
                l[right]=max(l[right],p[i]-1);
                r[i-p[i]+1]=max(r[i-p[i]+1],p[i]-1);
            }
            else{
                int j=2*pos-i;
                if(i+p[j]-1<right)p[i]=p[j];
                else if(i+p[j]-1>right)p[i]=right-i+1;
                else{
                    p[i]=p[j];
                    while(s[i-p[i]]==s[i+p[i]]&&i-p[i]>=0&&i+p[i]<=lens)p[i]++;
                    right=(i+p[i]-1);
                    pos=i;
                }
                l[i+p[i]-1]=max(l[i+p[i]-1],p[i]-1);
                r[i-p[i]+1]=max(r[i-p[i]+1],p[i]-1);
            }
        }
    }
    int main()
    {
        scanf("%s",s0+1);
        int len=strlen(s0+1);
        lens=len*2;
        for(int i=1;i<=len;i++){
            s[i*2-1]=s0[i];
            s[i*2]='#';
        }
        s[0]='#';
        manacher();
        for(int i=2;i<=lens;i+=2){
            r[i]=max(r[i],r[i-2]-2);
        }
        for(int i=lens;i>=1;i-=2){
            l[i]=max(l[i],l[i+2]-2);
        }
        for(int i=2;i<=lens;i+=2){
            if(l[i]&&r[i])ans=max(ans,l[i]+r[i]);
        }
        printf("%d
    ",ans);
        return 0;
    }
    P4555 [国家集训队]最长双回文串

     

    P5496 【模板】回文自动机(PAM)

    ·从一个节点连出去新边新建节点的时候,处理完新节点的fail再把新节点赋给原节点的儿子指针,不然一定出现我跳我自己的死循环。(原因是撞在了边界上)

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    const int S=5e5+10;
    char s[S];
    struct PAM{
        int ch[26],len,num,fail;
    }a[S];
    int lst,tot=1,lens,k;
    void build(){
        a[0].fail=1,a[1].fail=1;
        a[0].len=0,a[1].len=-1;
    }
    int getfail(int x,int y){
        while(s[y-a[x].len-1]!=s[y])x=a[x].fail;
        return x;
    }
    void insert(int x){
        int pos=getfail(lst,x);
        if(!a[pos].ch[s[x]-'a']){
            a[++tot].len=a[pos].len+2;
            a[tot].fail=a[getfail(a[pos].fail,x)].ch[s[x]-'a'];
            a[tot].num=a[a[tot].fail].num+1;
            a[pos].ch[s[x]-'a']=tot;//!
        }
        lst=a[pos].ch[s[x]-'a'];
    }
    int main()
    {
        scanf("%s",s+1);
        lens=strlen(s+1);
        build();
        for(int i=1;i<=lens;i++){
            s[i]=(s[i]-97+k)%26+97;
            insert(i);
            printf("%d ",a[lst].num);
            k=a[lst].num;
        }
        return 0;
    } 
    P5496 【模板】回文自动机(PAM)

     P3649 [APIO2014]回文串

    ·和上面的AC自动机不同,回文自动机里的节点建立是有序的,可以将统计信息用循环加回fail。

    ·不开long long见祖宗,下次认真算一下…什么时候了还犯这问题。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    char s[300010];
    struct PAM{
        int ch[26],fail,len,cnt;
    }a[300010];
    int lens,lst,tot=1;
    long long ans;
    void build(){
        a[0].fail=a[1].fail=1;
        a[0].len=0,a[1].len=-1;
    }
    int getfail(int x,int y){
        while(s[y-a[x].len-1]!=s[y])x=a[x].fail;
        return x;
    }
    void insert(int x){
        int pos=getfail(lst,x);
        if(!a[pos].ch[s[x]-'a']){
            a[++tot].len=a[pos].len+2;
            a[tot].cnt=1;
            a[tot].fail=a[getfail(a[pos].fail,x)].ch[s[x]-'a'];
            a[pos].ch[s[x]-'a']=tot;
        }
        else a[a[pos].ch[s[x]-'a']].cnt++;
        lst=a[pos].ch[s[x]-'a'];
    }
    int main()
    {
        scanf("%s",s+1);
        lens=strlen(s+1);
        build();
        for(int i=1;i<=lens;i++)insert(i);
        for(int i=tot;i>1;i--){
            a[a[i].fail].cnt+=a[i].cnt;
            ans=max(ans,1ll*a[i].len*a[i].cnt);
        }
        printf("%lld
    ",ans);
        return 0;
    }
    P3649 [APIO2014]回文串

    P4287 [SHOI2011]双倍回文

    ·记录一个和fail类似的指针half,表示不超过长度一半的最长后缀,减少需要跳的次数。

    ·从原节点的half开始寻找新节点的half,注意和新节点的len/2比较大小时要加上2,补上头尾两个字母。由此得知,当新节点的len<=2时,直接让它的half指针和fail指针相同,否则比较时出现死循环。

    ·困成智障就不要写题,一个+2愣是调了20min。

    #include<iostream>
    #include<cstdio>
    using namespace std;
    int lens,lst,tot=1,ans;
    char s[500010];
    struct PAM{
        int len,fail,ch[26],half;
    }a[500010];
    void build(){
        a[0].fail=a[1].fail=1;
        a[0].half=a[1].half=1;
        a[0].len=0,a[1].len=-1;
    }
    int getfail(int x,int y){
        while(s[y-a[x].len-1]!=s[y])x=a[x].fail;
        return x;
    }
    int gethalf(int x,int y){
        while(s[y-a[x].len-1]!=s[y]||a[x].len*2+4>a[tot].len)x=a[x].fail;
        return x;
    }
    void insert(int x){
        int pos=getfail(lst,x);
        if(!a[pos].ch[s[x]-'a']){
            a[++tot].len=a[pos].len+2;
            a[tot].fail=a[getfail(a[pos].fail,x)].ch[s[x]-'a'];
            if(a[tot].len<=2)a[tot].half=a[tot].fail;
            else a[tot].half=a[gethalf(a[pos].half,x)].ch[s[x]-'a'];
            if((a[a[tot].half].len%2==0)&&(a[a[tot].half].len*2==a[tot].len))ans=max(ans,a[tot].len);
            a[pos].ch[s[x]-'a']=tot;
        }
        lst=a[pos].ch[s[x]-'a'];
    }
    int main()
    {
        scanf("%d",&lens);
        scanf("%s",s+1);
        build();
        for(int i=1;i<=lens;i++){
            insert(i);
        }
        printf("%d",ans);
        return 0;
    } 
    P4287 [SHOI2011]双倍回文

    后缀数组:

    P3809 【模板】后缀排序

    ·终于彻底理解这个排序了

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    const int N=1e6+10;
    char s[N];
    int x[N],y[N],c[N],sa[N],n,m;
    void getsa(){
        for(int i=1;i<=n;i++)c[x[i]=s[i]]++;
        for(int i=2;i<=m;i++)c[i]+=c[i-1];
        for(int i=n;i>=1;i--)sa[c[x[i]]--]=i;
        for(int k=1;k<=n;k<<=1){
            int num=0;
            for(int i=n-k+1;i<=n;i++)y[++num]=i;
            for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k;
            for(int i=1;i<=m;i++)c[i]=0;
            for(int i=1;i<=n;i++)c[x[i]]++;
            for(int i=2;i<=m;i++)c[i]+=c[i-1];
            for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0;
            swap(x,y);
            x[sa[1]]=1;
            num=1;
            for(int i=2;i<=n;i++){
                x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
            }
            if(num==n)break;
            m=num;
        }
    }
    int main()
    {
        scanf("%s",s+1);
        n=strlen(s+1);
        m=122;
        getsa();
        for(int i=1;i<=n;i++)printf("%d ",sa[i]);
        return 0;
    }
    P3809 【模板】后缀排序

    P2852 [USACO06DEC]牛奶模式Milk Patterns

    ·注意细节

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int N=40010;
    int n,kk,ans;
    int s[N],x[N],y[N],sa[N],c[N],a[N];
    int height[N],m,rk[N];
    void getsa(){
        for(int i=1;i<=n;i++)c[x[i]=s[i]]++;
        for(int i=2;i<=m;i++)c[i]+=c[i-1];
        for(int i=n;i>=1;i--)sa[c[x[i]]--]=i;
        for(int k=1;k<=n;k<<=1){
            int num=0;
            for(int i=n-k+1;i<=n;i++)y[++num]=i;
            for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k;
            for(int i=1;i<=m;i++)c[i]=0;
            for(int i=1;i<=n;i++)c[x[i]]++;
            for(int i=1;i<=m;i++)c[i]+=c[i-1];
            for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0;
            swap(x,y);
            x[sa[1]]=1;
            num=1;
            for(int i=2;i<=n;i++){
                x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
            }
            if(num==n)break;
            m=num;
        }
    }
    void getheight(){
        for(int i=1;i<=n;i++)rk[sa[i]]=i;
        int k=0;
        for(int i=1;i<=n;i++){
            if(rk[i]==1){
                height[1]=k=0;
                continue;
            }
            if(k)k--;
            int j=sa[rk[i]-1];
            while(j+k<=n&&i+k<=n&&s[j+k]==s[i+k])k++;
            height[rk[i]]=k;
        }
    }
    int check(int x){
        int num=0;
        for(int i=1;i<=n;i++){
            if(height[i]>=x)num++;
            else num=0;
            if(num>=kk-1)return 1;
        }
        return 0;
    }
    int main()
    {
        scanf("%d%d",&n,&kk);
        for(int i=1;i<=n;i++)scanf("%d",&s[i]),a[i]=s[i];
        sort(a+1,a+n+1);
        m=unique(a+1,a+n+1)-a-1;
        for(int i=1;i<=n;i++){
            s[i]=lower_bound(a+1,a+m+1,s[i])-a;
        }
        getsa();
        getheight();
        int l=0,r=n;
        while(l<=r){
            int mid=(l+r)/2;
            if(check(mid)){
                ans=mid;
                l=mid+1;
            }
            else r=mid-1;
        }
        printf("%d
    ",ans);
        return 0;
    }
    P2852 [USACO06DEC]牛奶模式Milk Patterns

    P4248 [AHOI2013]差异(后缀数组)

    ·利用lcp(i,k)=min(height(j))(i+1<=j<=k)的性质,处理出每个height能为哪一段作出贡献。

    ·最后单调栈内剩下的元素的右端点要记得处理。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    const int N=1e6+10;
    char s[N];
    int n,m;
    int x[N],y[N],c[N],sa[N],rk[N],height[N];
    long long ans;
    int stack[N],top,l[N],r[N];
    void getsa(){
        for(int i=1;i<=n;i++)c[x[i]=s[i]]++;
        for(int i=2;i<=m;i++)c[i]+=c[i-1];
        for(int i=n;i>=1;i--)sa[c[x[i]]--]=i;
        for(int k=1;k<=n;k<<=1){
            int num=0;
            for(int i=n-k+1;i<=n;i++)y[++num]=i;
            for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k;
            for(int i=1;i<=m;i++)c[i]=0;
            for(int i=1;i<=n;i++)c[x[i]]++;
            for(int i=1;i<=m;i++)c[i]+=c[i-1];
            for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0;
            swap(x,y);
            x[sa[1]]=1;
            num=1;
            for(int i=2;i<=n;i++){
                x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
            }
            if(num==n)break;
            m=num;
        }
    }
    void getheight(){
        for(int i=1;i<=n;i++)rk[sa[i]]=i;
        int k=0;
        for(int i=1;i<=n;i++){
            if(rk[i]==1){
                height[1]=k=0;
                continue;
            }
            if(k)k--;
            int j=sa[rk[i]-1];
            while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k])k++;
            height[rk[i]]=k;
        }
    }
    int main()
    {
        scanf("%s",s+1);
        n=strlen(s+1);
        m=122;
        getsa();
        getheight();
        ans=1ll*(n-1)*n*(n+1)/2;
        for(int i=1;i<=n;i++){
            while(top&&height[stack[top]]>=height[i])r[stack[top]]=i,top--;
            l[i]=stack[top];
            stack[++top]=i;
        }
        while(top){
            r[stack[top]]=n+1;
            top--;
        }
        for(int i=1;i<=n;i++){
            ans-=2ll*(i-l[i])*(r[i]-i)*height[i];
        }
        printf("%lld
    ",ans);
        return 0;
     } 
    P4248 [AHOI2013]差异(后缀数组)

    P2463 [SDOI2008]Sandy的卡片

    ·check里注意符合条件的区间边界

    ·注意细节

    #include<iostream>
    #include<cstdio>
    using namespace std;
    const int N=4e5+10;
    int n,l,r,m,lst,ans,vis[N],t[N],top,sum,tim=3866;
    int s[N],cnt,x[N],y[N],c[N],sa[N],height[N],rk[N],id[N],rec[N];
    void getsa(){
        for(int i=1;i<=cnt;i++)c[x[i]=s[i]]++;
        for(int i=2;i<=m;i++)c[i]+=c[i-1];
        for(int i=cnt;i>=1;i--)sa[c[x[i]]--]=i;
        for(int k=1;k<=cnt;k<<=1){
            int num=0;
            for(int i=cnt-k+1;i<=cnt;i++)y[++num]=i;
            for(int i=1;i<=cnt;i++)if(sa[i]>k)y[++num]=sa[i]-k;
            for(int i=1;i<=m;i++)c[i]=0;
            for(int i=1;i<=cnt;i++)c[x[i]]++;
            for(int i=1;i<=m;i++)c[i]+=c[i-1];
            for(int i=cnt;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0;
            swap(x,y);
            x[sa[1]]=1;
            num=1;
            for(int i=2;i<=cnt;i++){
                x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
            }
            if(num==cnt)break;
            m=num;
        }
    }
    void getheight(){
        for(int i=1;i<=cnt;i++)rk[sa[i]]=i;
        int k=0;
        for(int i=1;i<=cnt;i++){
            if(rk[i]==1){
                height[1]=0;
                k=0;
                id[1]=rec[i];
                continue;
            }
            if(k)k--;
            int j=sa[rk[i]-1];
            while(s[i+k]==s[j+k]&&i+k<=cnt&&j+k<=cnt)k++;
            height[rk[i]]=k;
            id[rk[i]]=rec[i];
        }
    }
    int check(int x){
        while(top)vis[t[top--]]=0;
        sum=0;
        for(int i=1;i<=cnt;i++){
            if(height[i]<x){
                while(top)vis[t[top--]]=0;
                sum=0;
            }
            else{
                if(!top){
                    vis[id[i-1]]=1;
                    t[++top]=id[i-1];
                    sum++;
                    if(sum==n)return 1;
                }
                t[++top]=id[i];
                if(!vis[t[top]]){
                    vis[t[top]]=1;
                    sum++;
                    if(sum==n)return 1;
                }
            }
        }
        return 0;
    }
    int main()
    {
        scanf("%d",&n);
        r=N;
        for(int i=1,a;i<=n;i++){
            scanf("%d",&a);
            r=min(r,a-1);
            for(int j=1,b;j<=a;j++){
                scanf("%d",&b);
                if(j==1){
                    lst=b;
                    continue;
                }
                s[++cnt]=b-lst+2000;
                rec[cnt]=i;
                lst=b;
            }
            s[++cnt]=++tim;
            rec[cnt]=i;
        }
        m=tim;
        getsa();
        getheight();
        l=1;
        while(l<=r){
            int mid=(l+r)/2;
            if(check(mid)){
                ans=mid;
                l=mid+1;
            }
            else r=mid-1;
        }
        printf("%d
    ",ans+1);
        return 0;
    }
    //sa[c[x[y[i]]]--]=y[i]而不是=i,sa记录的是位置, y记录第二关键字对应的第一关键字的位置,i是排名
    //check之前清空记录的数组
    //二分注意细节 
    P2463 [SDOI2008]Sandy的卡片

    P5546 [POI2000]公共串

    ·和上一题其实一个题意,写了二分check的另一种方法

    ·注意作为分隔符的数字要互不相同,否则至少产生1的贡献(或者不把它们打上属于某个串的标记)。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    const int N=2e5+10;
    int tim,s[N],n,lst=0,x[N],y[N],c[N],m,sa[N],height[N],rk[N],rec[N],rea[N],l,r,ans;
    char s0[N];
    void getsa(){
        for(int i=1;i<=lst;i++)c[x[i]=s[i]]++;
        for(int i=1;i<=m;i++)c[i]+=c[i-1];
        for(int i=lst;i>=1;i--)sa[c[x[i]]--]=i;
        for(int k=1;k<=lst;k<<=1){
            int num=0;
            for(int i=lst-k+1;i<=lst;i++)y[++num]=i;
            for(int i=1;i<=lst;i++)if(sa[i]>k)y[++num]=sa[i]-k;
            for(int i=1;i<=m;i++)c[i]=0;
            for(int i=1;i<=lst;i++)c[x[i]]++;
            for(int i=1;i<=m;i++)c[i]+=c[i-1];
            for(int i=lst;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0;
            swap(x,y);
            x[sa[1]]=1;
            num=1;
            for(int i=2;i<=lst;i++){
                x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
            }
            if(num==lst)break;
            m=num;
        }
    }
    void getheight(){
        for(int i=1;i<=lst;i++)rk[sa[i]]=i;
        int k=0;
        for(int i=1;i<=lst;i++){
            if(rk[i]==1){
                height[1]=k=0;
                rea[1]=rec[i];
                continue;
            }
            if(k)k--;
            int j=sa[rk[i]-1];
            while(j+k<=lst&&i+k<=lst&&s[i+k]==s[j+k])k++;
            height[rk[i]]=k;
            rea[rk[i]]=rec[i];
        }
    }
    int t[N],top,vis[N];
    int check(int x){
        while(top)vis[t[top--]]=0;
        int sum=0;
        for(int i=1;i<=lst;i++){
            if(height[i]<x){
                while(top)vis[t[top--]]=0;
                sum=0;
            }
            if(!vis[rea[i]]){
                vis[rea[i]]=1;
                t[++top]=rea[i];
                sum++;
                if(sum==n)return 1;
            }
        }
        return 0;
    }
    int main(){
        scanf("%d",&n);
        r=N;
        tim=30;
        for(int i=1;i<=n;i++){
            scanf("%s",s0+1);
            int lens=strlen(s0+1);
            r=min(r,lens);
            for(int j=1;j<=lens;j++){
                s[++lst]=s0[j]-'a'+1;
                rec[lst]=i;
            }
            s[++lst]=++tim;
            rec[lst]=i;
        }
        m=tim;
        getsa();
        getheight();
        l=0;
        while(l<=r){
            int mid=(l+r)/2;
            if(check(mid)){
                ans=mid;
                l=mid+1;
            }
            else r=mid-1;
        }
        printf("%d
    ",ans);
        return 0;
    }
    //注意作为分隔符的字符要互不相同,否则至少会被记为1的答案 
    P5546 [POI2000]公共串

    P4094 [HEOI2016/TJOI2016]字符串

    ·预处理log2,不然复杂度是nlog^3n

    ·st表总层数不用预处理出来的log数组确定而是循环确定的话会多一层,st数组的层数再开大1

    ·二分的时候注意边界。

    ·关于答案的二分注意最长不超过min(a->b,c->d)。

    ·内部确定区间左右边界的二分查询最小值时要让左端点加一(height数组存的是与前一个的lcp)。

    ·主席树查询的时候注意要确定是否出现在区间内的点是从a->b-x+1的,因为要保证当前验证的答案长度x符合是a->b的字串。

    ·注意主席树的数组要开大一些。可以不用预处理T[0]。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    const int N=1e5+10;
    int n,m,lim,ans;
    char s[N];
    int x[N],y[N],c[N],sa[N],height[N],rk[N],tot,log[N];
    int st[N][17],T[N],L[1800010],R[1800010],cnt[1800010];
    void getsa(){
        for(int i=1;i<=n;i++)c[x[i]=s[i]]++;
        for(int i=1;i<=lim;i++)c[i]+=c[i-1];
        for(int i=1;i<=n;i++)sa[c[x[i]]--]=i;
        for(int k=1;k<=n;k<<=1){
            int num=0;
            for(int i=n-k+1;i<=n;i++)y[++num]=i;
            for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k;
            for(int i=1;i<=lim;i++)c[i]=0;
            for(int i=1;i<=n;i++)c[x[i]]++;
            for(int i=1;i<=lim;i++)c[i]+=c[i-1];
            for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0;
            swap(x,y);
            x[sa[1]]=num=1;
            for(int i=2;i<=n;i++){
                x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
            }
            if(n==num)break;
            lim=num;
        }
    }
    void getheight(){
        for(int i=1;i<=n;i++)rk[sa[i]]=i;
        int k=0;
        for(int i=1;i<=n;i++){
            if(rk[i]==1){
                height[1]=k=0;
                continue;
            }
            if(k)k--;
            int j=sa[rk[i]-1];
            while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k])k++;
            height[rk[i]]=k;
        }
    }
    void add(int &p,int pre,int l,int r,int pos,int val){
        p=++tot;
        L[p]=L[pre],R[p]=R[pre],cnt[p]=cnt[pre]+1;
        if(l==r)return;
        int mid=(l+r)/2;
        if(pos<=mid)add(L[p],L[pre],l,mid,pos,val);
        else add(R[p],R[pre],mid+1,r,pos,val);
    }
    int query(int p,int pre,int l,int r,int ll,int rr){
        if(ll<=l&&r<=rr){
            return cnt[p]-cnt[pre];
        }
        int mid=(l+r)/2;
        if(rr<=mid)return query(L[p],L[pre],l,mid,ll,rr);
        else if(ll>mid)return query(R[p],R[pre],mid+1,r,ll,rr);
        else return query(L[p],L[pre],l,mid,ll,mid)+query(R[p],R[pre],mid+1,r,mid+1,rr);
    }
    void work(){
    //    memset(st,0x3f3f3f3f,sizeof(st));
        for(int i=1;i<=n;i++)st[i][0]=height[i];
        st[1][0]=N; 
        int maxn,sum;
        for(int i=1;i<=log[n];i++){
            int k=(1<<(i-1));
            for(int j=1;j<=n-k;j++){
                st[j][i]=min(st[j][i-1],st[j+k][i-1]);
            }
        }
    }
    int cal(int lon,int l,int r){
        int sum=r-l+1;
        int val=min(st[l][log[sum]],st[r-(1<<log[sum])+1][log[sum]]);
        return val>=lon;
    }
    int check(int x,int a,int b,int c,int d){
        int now=rk[c];
        int l,r,ll=now,rr=now;
        l=1,r=now-1;
        while(l<=r){
            int mid=(l+r)/2;
            if(cal(x,mid+1,now)){
                ll=mid;
                r=mid-1;
            }
            else l=mid+1;
        }
        l=now+1,r=n;
        while(l<=r){
            int mid=(l+r)/2;
            if(cal(x,now+1,mid)){
                rr=mid;
                l=mid+1;
            }
            else r=mid-1;
        }
        return query(T[rr],T[ll-1],1,n,a,b-x+1);
    }
    int main(){
        scanf("%d%d",&n,&m);
        scanf("%s",s+1);
        int x=1,y=0;
        log[1]=0;
        for(int i=2;i<=n;i++){
            if(i==(x<<1)){
                x=i;
                y++;
            }
            log[i]=y;
        }
        lim=122;
        getsa();
        getheight();
        for(int i=1;i<=n;i++){
            add(T[i],T[i-1],1,n,sa[i],1);
        }
        work();
        for(int i=1,a,b,c,d;i<=m;i++){
            scanf("%d%d%d%d",&a,&b,&c,&d);
            int l=1,r=min(d-c+1,b-a+1);
            ans=0;
            while(l<=r){
                int mid=(l+r)/2;
                if(check(mid,a,b,c,d)){
                    ans=mid;
                    l=mid+1;
                }
                else r=mid-1;
            }
            printf("%d
    ",ans);
        }
        return 0;
    }
    //预处理log2,不然复杂度是nlog^3n
    //st表总层数不用预处理出来的log数组确定而是循环确定的话会多一层,st数组的层数再开大1
    //二分的时候注意边界。
    //关于答案的二分注意最长不超过min(a->b,c->d)。
    //内部确定区间左右边界的二分查询最小值时要让左端点加一(height数组存的是与前一个的lcp)。
    //主席树查询的时候注意要确定是否出现在区间内的点是从a->b-x+1的,因为要保证当前验证的答案长度x符合是a->b的字串。 
    //注意主席树的数组要开大一些。可以不用预处理T[0]。 
    P4094 [HEOI2016/TJOI2016]字符串

    P2178 [NOI2015]品酒大会

    ·看数据范围…把初值设置到合适的大小,至少1e15肯定不行。

    #include<iostream>
    #include<cstdio>
    #include<vector>
    #define ll long long
    using namespace std;
    const int N=6e5+10;
    const long long inf=1e18;
    ll n,m,x[N],y[N],c[N],sa[N],rk[N],height[N],fa[N];
    ll a[N],ans1[N],ans=-inf,val1,ans2[N];
    char s[N];
    struct node{
        ll maxx,minn;
        ll num,siz;
    }b[N];
    vector<ll>v[N];
    void getsa(){
        for(int i=1;i<=n;i++)c[x[i]=s[i]]++;
        for(int i=1;i<=m;i++)c[i]+=c[i-1];
        for(int i=n;i>=1;i--)sa[c[x[i]]--]=i;
        for(int k=1;k<=n;k<<=1){
            int num=0;
            for(int i=n-k+1;i<=n;i++)y[++num]=i;
            for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k;
            for(int i=1;i<=m;i++)c[i]=0;
            for(int i=1;i<=n;i++)c[x[i]]++;
            for(int i=1;i<=m;i++)c[i]+=c[i-1];
            for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0;
            swap(x,y);
            x[sa[1]]=num=1;
            for(int i=2;i<=n;i++){
                x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
            }
            if(num==n)break;
            m=num;
        }
    }
    void getheight(){
        for(int i=1;i<=n;i++)rk[sa[i]]=i;
        int k=0;
        for(int i=1;i<=n;i++){
            if(rk[i]==1){
                height[1]=k=0;
                continue;
            }
            if(k)k--;
            int j=sa[rk[i]-1];
            while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k])k++;
            height[rk[i]]=k;
        }
    }
    int get(int x){
        if(x==fa[x])return x;
        else return fa[x]=get(fa[x]);
    }
    void work(int x,int y){
    //    if(b[x].siz<b[y].siz)swap(x,y);
        fa[y]=x;
        ll val=max(max(b[x].num,b[y].num),max(b[x].maxx*b[y].maxx,b[x].minn*b[y].minn));
        b[x].num=max(b[x].num,val);
        ans=max(ans,val);
        ll si=b[x].siz*b[y].siz;
        b[x].siz=b[x].siz+b[y].siz;
        val1+=si;
        b[x].maxx=max(b[x].maxx,b[y].maxx);
        b[x].minn=min(b[x].minn,b[y].minn);
    }
    int main()
    {
        scanf("%lld",&n);
        scanf("%s",s+1);
        for(int i=1;i<=n;i++){
            scanf("%lld",&a[i]);
        }
        m=122;
        getsa();
        getheight();
        for(int i=1;i<=n;i++){
            b[i].maxx=b[i].minn=a[sa[i]];
            b[i].siz=1;
            b[i].num=-inf;
            fa[i]=i;
        }
        for(int i=1;i<=n;i++)v[height[i]].push_back(i);
        for(int i=n-1;i>=0;i--){
            for(int j=0;j<v[i].size();j++){
                int x=v[i][j];
                work(get(x),get(x-1));
            }
            if(val1){
                ans1[i]=val1;
                ans2[i]=ans;
            }
        }
        for(int i=0;i<n;i++){
            printf("%lld %lld
    ",ans1[i],ans2[i]);
        }
        return 0;
     } 
    P2178 [NOI2015]品酒大会

     后缀自动机:

    P3804 【模板】后缀自动机

    ·后缀链接形成一棵树。树上从叶子到根累计信息,dfs。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    const int N=1e6+10;
    struct SAM{
        int link,len,cnt;
        int ch[26];
    }a[2*N];
    int lst,siz,n;
    long long ans;
    char s[N];
    int head[2*N],Next[2*N],tot,ver[2*N];
    void build(){
        a[0].link=-1;
        a[0].len=0;
        siz=1,lst=0;
    }
    void add(int x,int y){
        ver[++tot]=y;
        Next[tot]=head[x];
        head[x]=tot;
    }
    void insert(char c){
        int cur=++siz;
        a[cur].cnt=1;
        a[cur].len=a[lst].len+1;
        int p=lst;
        while(p!=-1&&!a[p].ch[c-'a']){
            a[p].ch[c-'a']=cur;
            p=a[p].link;
        }
        if(p==-1){
            a[cur].link=0;
        }
        else{
            int q=a[p].ch[c-'a'];
            if(a[p].len+1==a[q].len){
                a[cur].link=q;
            }
            else{
                int clone=++siz;
                a[clone].len=a[p].len+1;
                a[clone].link=a[q].link;
                for(int i=0;i<26;i++)a[clone].ch[i]=a[q].ch[i];
                while(p!=-1&&a[p].ch[c-'a']==q){
                    a[p].ch[c-'a']=clone;
                    p=a[p].link;
                }
                a[q].link=a[cur].link=clone;
            }
        }
        lst=cur;
    }
    void dfs(int x){
        for(int i=head[x];i;i=Next[i]){
            int y=ver[i];
            dfs(y);
            a[x].cnt+=a[y].cnt;
        }
        if(a[x].cnt>1)ans=max(ans,1ll*a[x].cnt*a[x].len);
    }
    int main()
    {
        scanf("%s",s+1);
        n=strlen(s+1);
        build();
        for(int i=1;i<=n;i++){
            insert(s[i]);
        }
        for(int i=1;i<=siz;i++){
            add(a[i].link,i);
        }
        dfs(0);
        printf("%lld
    ",ans);
        return 0;
    }
    P3804 【模板】后缀自动机

     P4070 [SDOI2016]生成魔咒

    ·后缀自动机的状态数上限为2n-1,结构体开两倍。(转移数的上限为3n-4。)

    ·统计子串数有两种方式,dp整个自动机是其中一种,这里询问是O(n)级别所以不合适。另一种是计算所有状态对应的子串数之和,一个状态v的子串数=len(v)-len(link(v)),len为状态统计的长度len也即此状态对应的最长子串长度。由后缀链接的定义可知,每个状态对应长度连续的一组子串且短串为长串后缀,而后缀链接到的状态包括长度由len(v)到minlen(v)第一个不符合相同结束点集合的子串。设v后缀链接到的状态为z,len(z)+1=minlen(v)。由于一个状态对应的子串长度连续且到len(z)这个长度就不属于当前状态,当前状态的子串个数就是len(v)-len(z)了。

    ·为使转移合法而拆开状态时,子串的个数是不变的。只需要在加入新的状态并找到link之后维护答案即可。

    ·字符集较大,使用map。(我还离了个完全没有必要的散) 

    #include<iostream>
    #include<cstdio>
    #include<map>
    #include<algorithm>
    using namespace std;
    const int N=1e5+10;
    int n,m,s[N],b[N],lst,siz;
    long long ans;
    struct SAM{
        int link,len;
        map<int,int>mp;
    }a[2*N];
    void build(){
        a[0].link=-1;
        a[0].len=0;
        lst=0,siz=1;
    }
    void insert(int c){
        int cur=++siz;
        a[cur].len=a[lst].len+1;
        int p=lst;
        while(p!=-1&&!a[p].mp.count(c)){
            a[p].mp[c]=cur;
            p=a[p].link;
        }
        if(p==-1){
            a[cur].link=0;
            ans+=a[cur].len;
        }
        else{
            int q=a[p].mp[c];
            if(a[p].len+1==a[q].len){
                a[cur].link=q;
                ans+=a[cur].len-a[q].len;
            }
            else{
                int clone=++siz;
                a[clone].len=a[p].len+1;
                a[clone].link=a[q].link;
                a[clone].mp=a[q].mp;
                while(p!=-1&&a[p].mp[c]==q){
                    a[p].mp[c]=clone;
                    p=a[p].link;
                }
                a[q].link=a[cur].link=clone;
                ans+=a[cur].len-a[clone].len;
            }
        }
        lst=cur;
    }
    int main()
    {
        scanf("%d",&n);
        build();
        for(int i=1;i<=n;i++){
            scanf("%d",&s[i]);
            b[i]=s[i];
        }
        sort(b+1,b+n+1);
        m=unique(b+1,b+n+1)-b-1;
        for(int i=1;i<=n;i++){
            s[i]=lower_bound(b+1,b+m+1,s[i])-b;
            insert(s[i]);
            printf("%lld
    ",ans);
        }
        return 0;
    }
    P4070 [SDOI2016]生成魔咒

    P3975 [TJOI2015]弦论

    ·dp求每个点对应路径数量的时候,注意不要重复计算一个点。

    ·t=1时建出link树求每个状态出现次数,dp时累计进去。t=0时让每个状态的出现次数都为1,不要忘记处理。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    const int N=5e5+10;
    char s[N],ans[N];
    int t,k,n,siz,lst,dep;
    struct SAM{
        int len,link,cnt,num;
        int ch[26];
    }a[N*2];
    void build(){
        a[0].len=0;
        a[0].link=-1;
        siz=0,lst=0;
    }
    int head[2*N],Next[2*N],tot,ver[2*N];
    void add(int x,int y){
        ver[++tot]=y;
        Next[tot]=head[x];
        head[x]=tot;
    }
    void insert(char c){
        int cur=++siz;
        a[cur].cnt=1;
        a[cur].len=a[lst].len+1;
        int p=lst;
        while(p!=-1&&!a[p].ch[c-'a']){
            a[p].ch[c-'a']=cur;
            p=a[p].link;
        }
        if(p==-1){
            a[cur].link=0;
        }
        else{
            int q=a[p].ch[c-'a'];
            if(a[p].len+1==a[q].len){
                a[cur].link=q;
            }
            else{
                int clone=++siz;
                a[clone].len=a[p].len+1;
                a[clone].link=a[q].link;
                for(int i=0;i<26;i++)a[clone].ch[i]=a[q].ch[i];
                while(p!=-1&&a[p].ch[c-'a']==q){
                    a[p].ch[c-'a']=clone;
                    p=a[p].link; 
                }
                a[cur].link=a[q].link=clone;
            }
        }
        lst=cur;
    }
    void dfs(int x){
        for(int i=head[x];i;i=Next[i]){
            int y=ver[i];
            dfs(y);
            a[x].cnt+=a[y].cnt;
        }
    }
    void dfs1(int x){
        a[x].num=a[x].cnt;
        for(int i=0;i<26;i++){
            if(a[x].ch[i]){
                if(!a[a[x].ch[i]].num)dfs1(a[x].ch[i]);
                a[x].num+=a[a[x].ch[i]].num;
            }
        }
    }
    void dfs2(int x,int sum){
        if(x&&a[x].cnt>=sum){
            return;
        }
        if(x)sum-=a[x].cnt;
        for(int i=0;i<26;i++){
            if(a[a[x].ch[i]].num>=sum){
                ans[++dep]='a'+i;
                dfs2(a[x].ch[i],sum);
                return;
            }
            else sum-=a[a[x].ch[i]].num;
        }
    }
    int main()
    {
        scanf("%s",s+1);
        n=strlen(s+1);
        scanf("%d%d",&t,&k);
        build();
        for(int i=1;i<=n;i++){
            insert(s[i]);
        }
        if(t==1){
            for(int i=1;i<=siz;i++){
                add(a[i].link,i);
            }
            dfs(0);
            a[0].cnt=0;
        }
        else{
            for(int i=1;i<=siz;i++){
                a[i].cnt=1;
            }
        }
        dfs1(0);
        if(a[0].num<k){
            printf("-1
    ");
            return 0;
        }
        a[0].num=0;
        dfs2(0,k);
        for(int i=1;i<=dep;i++){
            printf("%c",ans[i]);
        }
        return 0;
    }
    P3975 [TJOI2015]弦论

    P4248 [AHOI2013]差异(后缀自动机)

    ·灵活运用反转,前缀不好处理就转化成后缀。求后缀的公共前缀=反转以后求前缀的公共后缀。两个前缀的公共后缀=link树上的lca。

    ·统计路径经过哪些点处理贡献=考虑每条边被多少路径经过。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    const int N=1e6+10;
    int n,lst,siz;
    long long ans;
    char s[N];
    struct SAM{
        int link,cnt,len;
        int ch[26];
    }a[N];
    void build(){
        a[0].len=0;
        a[0].link=-1;
        lst=0,siz=0;
    }
    int ver[N],head[N],Next[N],tot,edge[N];
    void add(int x,int y,int z){
        ver[++tot]=y;
        Next[tot]=head[x];
        head[x]=tot;
        edge[tot]=z;
    }
    void insert(char c){
        int cur=++siz;
        a[cur].len=a[lst].len+1;
        a[cur].cnt=1;
        int p=lst;
        while(p!=-1&&!a[p].ch[c-'a']){
            a[p].ch[c-'a']=cur;
            p=a[p].link;
        }
        if(p==-1){
            a[cur].link=0;
        }
        else{
            int q=a[p].ch[c-'a'];
            if(a[p].len+1==a[q].len){
                a[cur].link=q;
            }
            else{
                int clone=++siz;
                a[clone].link=a[q].link;
                a[clone].len=a[p].len+1;
                for(int i=0;i<26;i++)a[clone].ch[i]=a[q].ch[i];
                while(p!=-1&&a[p].ch[c-'a']==q){
                    a[p].ch[c-'a']=clone;
                    p=a[p].link;
                }
                a[q].link=a[cur].link=clone;
            }
        }
        lst=cur;
    }
    int si[N];
    void dfs(int x,int lon){
        si[x]=a[x].cnt;
        for(int i=head[x];i;i=Next[i]){
            int y=ver[i];
            dfs(y,edge[i]);
            si[x]+=si[y];
        }
        ans+=1ll*si[x]*(n-si[x])*lon;
    }
    int main()
    {
        scanf("%s",s+1);
        n=strlen(s+1);
        for(int i=1;i<=n/2;i++){
            swap(s[i],s[n-i+1]);
        } 
        build();
        for(int i=1;i<=n;i++){
            insert(s[i]);
        }
        for(int i=1;i<=siz;i++){
            add(a[i].link,i,a[i].len-a[a[i].link].len);
        }
        dfs(0,0);
        printf("%lld
    ",ans);
        return 0;
     } 
    P4248 [AHOI2013]差异(后缀自动机)

    P3346 [ZJOI2015]诸神眷顾的幻想乡

    ·所有叶子节点两两之间的路径覆盖一棵树的所有子串:度为1的就是叶节点,以每个叶节点开始dfs一遍树构建广义SAM。

    ·广义SAM(在线)的正确写法,insert函数要比普通SAM多两个特判:

      1.进入insert函数以后,判断a[lst].ch[c]&&a[lst].len+1==a[a[lst].ch[c]].len,即是否存在和要新建的状态完全等价的状态,避免重复建点。

      2.新状态的link不指向0,且指向的是需要拆出来的状态时,判断a[p(last跳过link以后找到的状态)].len+1==a[cur(当前状态)].len,即拆出来的状态是不是和当前状态完全等价。如果等价,此时新节点为空节点,不承载任何独特信息,让last等于拆出来的状态。

    ·这是广义SAM的在线写法,离线可以建出trie树然后bfs建立SAM,dfs可能被卡成n2。离线不考虑上面的特判。

    ·注意细节,每次都要把last放在正确位置,以及while里不要忘记不断跳link。

    #include<iostream>
    #include<cstdio>
    using namespace std;
    const int N=1e5+10;
    int n,color,col[N];
    int ver[2*N],Next[2*N],head[N],tot,du[N];
    long long ans;
    int lst,si;
    struct SAM{
        int len,link;
        int ch[10];
    }a[30*N];
    void build(){
        a[0].link=-1;
    }
    void add(int x,int y){
        ver[++tot]=y;
        Next[tot]=head[x];
        head[x]=tot;
    }
    void insert(int c){
        if(a[lst].ch[c]&&a[lst].len+1==a[a[lst].ch[c]].len){
            lst=a[lst].ch[c];
            return;
        }
        int cur=++si,flag=0,clone;
        a[cur].len=a[lst].len+1;
        int p=lst;
        while(p!=-1&&!a[p].ch[c]){
            a[p].ch[c]=cur;
            p=a[p].link;
        }
        if(p==-1){
            a[cur].link=0;
        }
        else{
            int q=a[p].ch[c];
            if(a[p].len+1==a[q].len){
                a[cur].link=q;
            }
            else{
                if(a[p].len+1==a[cur].len)flag=1;
                clone=++si;
                a[clone].len=a[p].len+1;
                a[clone].link=a[q].link;
                for(int i=0;i<color;i++)a[clone].ch[i]=a[q].ch[i];
                while(p!=-1&&a[p].ch[c]==q){
                    a[p].ch[c]=clone;
                    p=a[p].link;
                }
                a[q].link=a[cur].link=clone;
            }
        }
        lst=(flag?clone:cur);
    }
    void dfs1(int x,int fa){
        insert(col[x]);
        int now=lst;
        for(int i=head[x];i;i=Next[i]){
            int y=ver[i];
            if(y==fa)continue;
            dfs1(y,x);
            lst=now;
        }
    }
    int main()
    {
        scanf("%d%d",&n,&color);
        for(int i=1;i<=n;i++){
            scanf("%d",&col[i]);
        }
        for(int i=1,x,y;i<n;i++){
            scanf("%d%d",&x,&y);
            add(x,y),add(y,x);
            du[x]++,du[y]++;
        }
        build();
        for(int i=1;i<=n;i++){
            if(du[i]==1){
                lst=0;
                dfs1(i,0);
            }
        }
        for(int i=1;i<=si;i++)ans+=a[i].len-a[a[i].link].len;
        printf("%lld
    ",ans);
        return 0;
     } 
    P3346 [ZJOI2015]诸神眷顾的幻想乡

    P2336 [SCOI2012]喵星球上的点名

    ·注意莫队里面指针变化的细节

    ·莫队是先按左端点排序分块再在块内部按右端点排序的,我写了个假莫队…

    #include<iostream>
    #include<cstdio>
    #include<map>
    #include<algorithm>
    #include<vector>
    using namespace std;
    const int N=5e4+10,M=1e5+10;
    int n,m,siz,lst,b[M],lens;
    int head[M],Next[M],tot,ver[M],rec[M],rec1[M],tim,cnt,rea[M];
    int ans[M],ans1,sum[M],liv[M],fir[M];
    struct node{
        int len,link;
        map<int,int>mp;
        vector<int>v;
    }a[M*2];
    struct que{
        int l,r,id;
    }e[M];
    void build(){
        a[0].link=-1;
    }
    void add(int x,int y){
        ver[++tot]=y;
        Next[tot]=head[x];
        head[x]=tot;
    }
    void insert(int c,int id){
        if(a[lst].mp.count(c)&&a[a[lst].mp[c]].len==a[lst].len+1){
            lst=a[lst].mp[c];
            a[lst].v.push_back(id);
            return;
        }
        int cur=++siz;
        a[cur].len=a[lst].len+1;
        a[cur].v.push_back(id);
        int p=lst,flag=0,clone;
        while(p!=-1&&!a[p].mp.count(c)){
            a[p].mp[c]=cur;
            p=a[p].link;
        }
        if(p==-1){
            a[cur].link=0;
        }
        else{
            int q=a[p].mp[c];
            if(a[p].len+1==a[q].len){
                a[cur].link=q;
            }
            else{
                clone=++siz;
                a[clone].len=a[p].len+1;
                a[clone].mp=a[q].mp;
                a[clone].link=a[q].link;
                if(p==lst)flag=1;
                while(p!=-1&&a[p].mp[c]==q){
                    a[p].mp[c]=clone;
                    p=a[p].link;
                }
                a[cur].link=a[q].link=clone;
            }
        }
        lst=(flag?clone:cur);
    }
    void dfs(int x){
        rec[x]=++tim;
        rea[tim]=x;
        for(int i=head[x];i;i=Next[i]){
            int y=ver[i];
            dfs(y);
        }
        rec1[x]=tim;
    } 
    void find(int num){
        int now=0;
        for(int i=1;i<=lens;i++){
            if(a[now].mp.count(b[i]))now=a[now].mp[b[i]];
            else return;
        }
        e[++cnt].l=rec[now],e[cnt].r=rec1[now],e[cnt].id=num;
    }
    void putin(int x,int t){
        if(sum[x]==0){
            ans1++;
            fir[x]=t;
        }
        sum[x]++;
    }
    void del(int x,int t){
        sum[x]--;
        if(sum[x]==0){
            ans1--;
            liv[x]+=t-fir[x];
        }
    }    
    bool cmp(que a,que b){
        return (a.l==b.l)?(a.r<b.r):(a.l<b.l);
    }
    int main()
    {
        scanf("%d%d",&n,&m);
        build();
        for(int i=1,x,y;i<=n;i++){
            lst=0;
            scanf("%d",&x);
            for(int j=1;j<=x;j++){
                scanf("%d",&y);
                insert(y,i);
            }
            lst=0;
            scanf("%d",&x);
            for(int j=1;j<=x;j++){
                scanf("%d",&y);
                insert(y,i);
            }
        }
        for(int i=1;i<=siz;i++)add(a[i].link,i);
        dfs(0);
        for(int i=1;i<=m;i++){
            scanf("%d",&lens);
            for(int j=1;j<=lens;j++)scanf("%d",&b[j]);
            find(i);
        }
        sort(e+1,e+cnt+1,cmp);
        for(int i=e[1].l;i<=e[1].r;i++){
            for(int j=0;j<a[rea[i]].v.size();j++){
                int x=a[rea[i]].v[j];
                putin(x,1);
            }
        }
        int l=e[1].l,r=e[1].r;
        ans[e[1].id]=ans1;
        for(int i=2;i<=cnt;i++){
            int ll=e[i].l,rr=e[i].r;
            while(l<ll){
                for(int j=0;j<a[rea[l]].v.size();j++){
                    int x=a[rea[l]].v[j];
                    del(x,i);
                }
                l++;
            }
            while(l>ll){
                l--;
                for(int j=0;j<a[rea[l]].v.size();j++){
                    int x=a[rea[l]].v[j];
                    putin(x,i);
                }
            }
            while(r<rr){
                r++;
                for(int j=0;j<a[rea[r]].v.size();j++){
                    int x=a[rea[r]].v[j];
                    putin(x,i);
                }
            }
            while(r>rr){
                for(int j=0;j<a[rea[r]].v.size();j++){
                    int x=a[rea[r]].v[j];
                    del(x,i);
                }
                r--;
            }
            ans[e[i].id]=ans1;
        }
        for(int i=l;i<=r;i++){
            for(int j=0;j<a[rea[i]].v.size();j++){
                int x=a[rea[i]].v[j];
                del(x,cnt+1);
            }
        }
        for(int i=1;i<=m;i++)printf("%d
    ",ans[i]);
        for(int i=1;i<=n;i++)printf("%d ",liv[i]);
        return 0;
     } 
    P2336 [SCOI2012]喵星球上的点名

    SP1812 LCS2 - Longest Common Substring II

    ·和上面的Sandy以及公共串一样是求n个串中的最长公共字串,后缀自动机的做法要注意各种标记(min&max)进行比较的顺序。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    const int N=3e5+10;
    int siz,lst,lens,num=1,first,ans,maxx[N];
    char s[N];
    struct node{
        int len,link,minn;
        int ch[26];
    }a[N];
    int ver[N],head[N],tot,Next[N];
    void add(int x,int y){
        ver[++tot]=y;
        Next[tot]=head[x];
        head[x]=tot;
    }
    void build(){
        a[0].link=-1;
    }
    void insert(char c){
        int cur=++siz;
        a[cur].len=a[lst].len+1;
        a[cur].minn=N;
        int p=lst;
        while(p!=-1&&!a[p].ch[c-'a']){
            a[p].ch[c-'a']=cur;
            p=a[p].link;
        }
        if(p==-1){
            a[cur].link=0;
        }
        else{
            int q=a[p].ch[c-'a'];
            if(a[p].len+1==a[q].len){
                a[cur].link=q;
            }
            else{
                int clone=++siz;
                a[clone].len=a[p].len+1;
                a[clone].link=a[q].link;
                a[clone].minn=N;
                for(int i=0;i<26;i++)a[clone].ch[i]=a[q].ch[i];
                while(p!=-1&&a[p].ch[c-'a']==q){
                    a[p].ch[c-'a']=clone;
                    p=a[p].link;
                }
                a[cur].link=a[q].link=clone;
            }
        }
        lst=cur;
    }
    void change(){
        int now=0,l=0;
        for(int i=1;i<=lens;i++){
            while(!a[now].ch[s[i]-'a']&&now!=0){
                now=a[now].link;
                l=min(l,a[now].len);
            }
            if(!now)l=0;
            if(a[now].ch[s[i]-'a']){
                now=a[now].ch[s[i]-'a'];
                l++;
                l=min(l,a[now].len);
                maxx[now]=max(maxx[now],l);
            }
        }
    }        
    void dfs(int x){
        for(int i=head[x];i;i=Next[i]){
            int y=ver[i];
            dfs(y);
            maxx[x]=max(maxx[x],maxx[y]);
        }
        a[x].minn=min(a[x].minn,min(maxx[x],a[x].len));
    }
    void dfs1(int x){
        ans=max(ans,a[x].minn);
        for(int i=head[x];i;i=Next[i]){
            int y=ver[i];
            dfs1(y);
        }
    }
    int main(){
        scanf("%s",s+1);
        build();
        lens=strlen(s+1);
        first=lens;
        for(int i=1;i<=lens;i++){
            insert(s[i]);
        }
        for(int i=1;i<=siz;i++)add(a[i].link,i);
        while(scanf("%s",s+1)!=EOF){
            memset(maxx,0,sizeof(maxx));
            num++;
            lens=strlen(s+1);
            change();
            dfs(0);
        }
        if(num==1)printf("%d
    ",first);
        else{
            dfs1(0);
            printf("%d
    ",ans);
        }
        return 0;
    }
    SP1812 LCS2 - Longest Common Substring II

    附带Sandy的卡片的后缀自动机版本

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<map>
    using namespace std;
    const int N=3e5+10;
    int fro,n,siz,lst,lens,num=1,first,ans,maxx[N];
    int s[N];
    struct node{
        int len,link,minn;
        map<int,int>mp;
    }a[N];
    int ver[N],head[N],tot,Next[N];
    void add(int x,int y){
        ver[++tot]=y;
        Next[tot]=head[x];
        head[x]=tot;
    }
    void build(){
        a[0].link=-1;
    }
    void insert(int c){
        int cur=++siz;
        a[cur].len=a[lst].len+1;
        a[cur].minn=N;
        int p=lst;
        while(p!=-1&&!a[p].mp.count(c)){
            a[p].mp[c]=cur;
            p=a[p].link;
        }
        if(p==-1){
            a[cur].link=0;
        }
        else{
            int q=a[p].mp[c];
            if(a[p].len+1==a[q].len){
                a[cur].link=q;
            }
            else{
                int clone=++siz;
                a[clone].len=a[p].len+1;
                a[clone].link=a[q].link;
                a[clone].minn=N;
                a[clone].mp=a[q].mp;
                while(p!=-1&&a[p].mp[c]==q){
                    a[p].mp[c]=clone;
                    p=a[p].link;
                }
                a[cur].link=a[q].link=clone;
            }
        }
        lst=cur;
    }
    void change(){
        int now=0,l=0;
        for(int i=1;i<=lens;i++){
            while(!a[now].mp.count(s[i])&&now!=0){
                now=a[now].link;
                l=min(l,a[now].len);
            }
            if(!now)l=0;
            if(a[now].mp.count(s[i])){
                now=a[now].mp[s[i]];
                l++;
                l=min(l,a[now].len);
                maxx[now]=max(maxx[now],l);
            }
        }
    }        
    void dfs(int x){
        for(int i=head[x];i;i=Next[i]){
            int y=ver[i];
            dfs(y);
            maxx[x]=max(maxx[x],maxx[y]);
        }
        a[x].minn=min(a[x].minn,min(maxx[x],a[x].len));
    }
    void dfs1(int x){
        ans=max(ans,a[x].minn);
        for(int i=head[x];i;i=Next[i]){
            int y=ver[i];
            dfs1(y);
        }
    }
    int main(){
        build();
        scanf("%d",&n);
        scanf("%d",&lens);
        first=lens;
        for(int i=1,x;i<=lens;i++){
            scanf("%d",&x);
            if(i!=1)insert(x-fro);
            fro=x;
        }
        for(int i=1;i<=siz;i++)add(a[i].link,i);
        for(int i=2;i<=n;i++){
            scanf("%d",&lens);
            for(int j=1,x;j<=lens;j++){
                scanf("%d",&x);
                if(j!=1)s[j-1]=x-fro;
                fro=x;
            }
            memset(maxx,0,sizeof(maxx));
            num++;
            change();
            dfs(0);
        }
        if(num==1)printf("%d
    ",first);
        else{
            dfs1(0);
            printf("%d
    ",ans+1);
        }
        return 0;
    }
    P2463 [SDOI2008]Sandy的卡片

    刚刚忘记留空行了(´;ω;`)编辑不能

    持续补完

    (给博客园的延迟烧个高香。)

    P2852 [USACO06DEC]牛奶模式Milk Patterns

  • 相关阅读:
    注解的作用(一)
    《Linux命令行与shell脚本编程大全 第3版》Linux命令行---13
    《Linux命令行与shell脚本编程大全 第3版》Linux命令行---12
    《Linux命令行与shell脚本编程大全 第3版》Linux命令行---11
    《Linux命令行与shell脚本编程大全 第3版》Linux命令行---7
    《Linux命令行与shell脚本编程大全 第3版》Linux命令行---10
    《Linux命令行与shell脚本编程大全 第3版》Linux命令行---6
    《Linux命令行与shell脚本编程大全 第3版》Linux命令行---5
    《Linux命令行与shell脚本编程大全 第3版》Linux命令行---4
    《Linux命令行与shell脚本编程大全 第3版》Linux命令行---3
  • 原文地址:https://www.cnblogs.com/chloris/p/12027424.html
Copyright © 2020-2023  润新知