• 「回文自动机」


    前言

    处理回文:

    • hash二分
    • 回文自动机
    • manacher
    #include<bits/stdc++.h>
    using namespace std;
    const int N=2e5+50;
    int n;
    char s[N];
    struct PAM{
        int ptr,lst;
        int fin[N],len[N],num[N],trans[N],ch[N][26],fail[N];
        inline int getfail(int x,int y){
            while(s[x-len[y]-1]!=s[x]) y=fail[y];
            return y;
        }
        inline int gettrans(int x,int y){
            while(s[x-len[y]-1]!=s[x]||(len[y]+2)*2>len[ptr]) y=fail[y];
            return y;
        }
        PAM(){
            lst=1; ptr=1;
            len[0]=0; len[1]=-1;
            fail[0]=1; fail[1]=1;
            trans[0]=1;trans[1]=1;
        }
        inline void insert(){
            for(int i=1;i<=n;++i){
                int cur=getfail(i,lst);
                if(!ch[cur][s[i]-'a']){
                    ++ptr;
                    len[ptr]=len[cur]+2;
                    fail[ptr]=ch[getfail(i,fail[cur])][s[i]-'a'];
                    ch[cur][s[i]-'a']=ptr;
                    num[ptr]=num[fail[ptr]]+1;
                    if(len[ptr]<=2) trans[ptr]=fail[ptr];
                    else trans[ptr]=ch[gettrans(i,trans[cur])][s[i]-'a'];
                }
                fin[i]=lst=ch[cur][s[i]-'a'];
            }
        }
    }f[2];
    int main(){
        scanf("%s",s+1);
        n=strlen(s+1);
        f[0].insert();
        reverse(s+1,s+n+1);
        f[1].insert();
        int ans=0;
        for(int i=1;i<n;++i) ans=max(ans,f[0].len[f[0].fin[i]]+f[1].len[f[1].fin[n-(i+1)+1]]);
        printf("%d
    ",ans);
        return 0;
    }
    最长双回文子串

    在板子里有一个问题

    fail[ptr]=ch[getfail(i,fail[cur])][s[i]-'a'];
    ch[cur][s[i]-'a']=ptr;

    就是一定要先处理出$fail$再把该点赋为$ptr$,因为先赋就可能会存在$fail[cur]=1$从而$fail[ptr]=ch[cur][s[i]-'a']=ptr$的存在。

    应用

    • 处理出以该点为结尾/开头的最长回文
    • 找本质不同的回文串数量
    • 找某个本质的回文串数量
    • 找所有回文串数量
    • 没了(不过好像这些就够了?)

    还能处理出以该点为结尾/开头的最长回文的最长回文后缀,以及长度小于最长回文一半的最长回文后缀。

    例题

    G. 双倍回文

    题意:计算满足后一半也是回文的最长回文。

    题解:

    在用回文自动机找最长回文时顺便处理出$trans$数组代表以该点为结尾,长度小于最长回文一半的最长回文后缀。

    那么满足题目条件的回文就是$len[trans[i]]==len_i>>1&&len[trans[i]]\%2==0$,注意不能用$len[trans[i]]&1xor1$替代,因为$-1&1xor1=0$。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=5e5+50;
    int lst,ptr,ch[N][26],num[N],trans[N],len[N],fail[N],n,ans;
    char s[N];
    inline int getfail(int x,int y){
        while(s[x-len[y]-1]!=s[x]) y=fail[y];
        return y;
    }
    inline int gettrans(int x,int y){
        while(s[x-len[y]-1]!=s[x]||((len[y]+2)<<1)>len[ptr]) y=fail[y];
        return y;
    }
    inline void init(){
        scanf("%d%s",&n,s+1);
    }
    inline void insert(){
        ptr=1,lst=1;
        fail[0]=1;fail[1]=1;
        len[0]=0;len[1]=-1;
        trans[0]=1;trans[1]=1;
        for(int i=1;i<=n;++i){
            int cur=getfail(i,lst);
            if(!ch[cur][s[i]-'a']){
                ++ptr;
                len[ptr]=len[cur]+2;
                fail[ptr]=ch[getfail(i,fail[cur])][s[i]-'a'];
                ch[cur][s[i]-'a']=ptr;
                if(len[ptr]<=2) trans[ptr]=fail[ptr];
                else trans[ptr]=ch[gettrans(i,trans[cur])][s[i]-'a'];
            }
            lst=ch[cur][s[i]-'a'];
            if(len[trans[lst]]*2==len[lst]&&len[trans[lst]]%2==0) ans=max(ans,len[lst]);
        }
    }
    inline void output(){
        printf("%d
    ",ans);
    }
    int main(){
        init();
        insert();
        output();
        return 0;
    }
    View Code

    H. 最长双回文串

    题意:$rt$。

    题解:

    回文自动机的话就正反建两棵回文自动机然后直接每个点更新答案。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=2e5+50;
    int n;
    char s[N];
    struct PAM{
        int ptr,lst;
        int fin[N],len[N],num[N],trans[N],ch[N][26],fail[N];
        inline int getfail(int x,int y){
            while(s[x-len[y]-1]!=s[x]) y=fail[y];
            return y;
        }
        inline int gettrans(int x,int y){
            while(s[x-len[y]-1]!=s[x]||(len[y]+2)*2>len[ptr]) y=fail[y];
            return y;
        }
        PAM(){
            lst=1; ptr=1;
            len[0]=0; len[1]=-1;
            fail[0]=1; fail[1]=1;
            trans[0]=1;trans[1]=1;
        }
        inline void insert(){
            for(int i=1;i<=n;++i){
                int cur=getfail(i,lst);
                if(!ch[cur][s[i]-'a']){
                    ++ptr;
                    len[ptr]=len[cur]+2;
                    fail[ptr]=ch[getfail(i,fail[cur])][s[i]-'a'];
                    ch[cur][s[i]-'a']=ptr;
                    num[ptr]=num[fail[ptr]]+1;
                    if(len[ptr]<=2) trans[ptr]=fail[ptr];
                    else trans[ptr]=ch[gettrans(i,trans[cur])][s[i]-'a'];
                }
                fin[i]=lst=ch[cur][s[i]-'a'];
            }
        }
    }f[2];
    int main(){
        scanf("%s",s+1);
        n=strlen(s+1);
        f[0].insert();
        reverse(s+1,s+n+1);
        f[1].insert();
        int ans=0;
        for(int i=1;i<n;++i) ans=max(ans,f[0].len[f[0].fin[i]]+f[1].len[f[1].fin[n-(i+1)+1]]);
        printf("%d
    ",ans);
        return 0;
    }
    View Code

    I. I Love Palindrome String

    题意:求有多少个字符串为回文串,且它的前一半也是回文串。

    题解:

    回文自动机的话依旧是维护$trans$数组,考虑如果前一半是回文串那么后一半就肯定也是回文串。

    但是这个前一半包括了奇回文的中心,所以维护$trans$就是要满足$len[trans[i]]==(len_i+1>>1)$。

    用$hash$或者后缀树也能做好像。

    #include<cstdio>
    #include<cstring>
    #define ll long long
    
    using namespace std;
    const int N=3e5+50;
    int n,ptr,lst,len[N],ch[N][26],fail[N],ok[N],num[N],trans[N];
    char s[N]; ll ret[N];
    int getfail(int x,int y){
        while(s[x-len[y]-1]!=s[x]) y=fail[y];
        return y;
    }
    int gettrans(int x,int y){
        while(s[x-len[y]-1]!=s[x]||len[y]+2>(len[ptr]+1)/2) y=fail[y];
        return y;
    }
    inline int NEW(){
        ++ptr;
        num[ptr]=len[ptr]=fail[ptr]=ok[ptr]=trans[ptr]=0;
        for(int i=0;i<26;++i) ch[ptr][i]=0;
        return ptr;
    }
    inline void init(){
        ptr=-1; lst=0;
        NEW(); NEW();
        len[0]=0; len[1]=-1;
        fail[0]=1; fail[1]=1;
    }
    inline void insert(){
        for(int i=1;s[i];++i){
            int cur=getfail(i,lst);
            if(!ch[cur][s[i]-'a']){
                NEW();
                len[ptr]=len[cur]+2;
                fail[ptr]=ch[getfail(i,fail[cur])][s[i]-'a'];
                ch[cur][s[i]-'a']=ptr;
                if(len[ptr]<=2) trans[ptr]=fail[ptr];
                else trans[ptr]=ch[gettrans(i,trans[cur])][s[i]-'a'];
                if(len[trans[ptr]]==(len[ptr]+1)/2) ok[ptr]=1;
            }
            lst=ch[cur][s[i]-'a'];
            num[lst]++;
        }
    }
    int main(){
        while(~scanf("%s",s+1)){
            init();
            insert();
            for(int i=ptr;i>1;--i) num[fail[i]]+=num[i];
            for(int i=2;i<=ptr;++i) if(ok[i]) ret[len[i]]+=num[i];
            ret[1]=strlen(s+1);
            for(int i=1;s[i];++i) printf("%lld%c",ret[i],s[i+1]?' ':'
    '),ret[i]=0;
        }
        return 0;
    }
    View Code

    J. Antisymmetry

    题意

    对于一个01字符串,如果将这个字符串0和1取反后,再将整个串反过来和原串一样,就称作“反对称”字符串。

    比如00001111和010101就是反对称的,1001就不是。 现在给出一个长度为N的01字符串,求它有多少个子串是反对称的。

    题解:

    发现反对称与回文的定义不同的是将相同变成了相反,那么就维护这样的反对称自动机,$fail$的条件变成了

    while(x!=1&&(s[n-len[x]-1]==s[n]||n-len[x]-1<1)) x=fail[x];

    $n-len_x-1<1$是为了防止越过边界判定错误,$x!=1$是因为如果到了1节点的话再接下来就会一直与自己匹配,这样会导致死循环,同时因为到了1节点说明一定是奇回文,而对于奇回文一定不是题目要求的,因为中间值取反后就一定不与原来相等了。

    还有一个不同点:

    if(s[n-len[prt]-1]==s[n]||n-len[prt]-1<1) {lst=0;continue;}

    在找到了该点的父亲之后,如果这个父亲条件不合法,就说明当前点不可能变成一个合法的回文串,于是就把$lst$赋成0,表示下一个字符重新来过。(应该是这样理解的叭)

    #include<bits/stdc++.h>
    using namespace std;
    const int N=5e5+50;
    
    int lst,ptr,n;
    int len[N],ch[N][26],fail[N],num[N];
    char s[N];
    inline void init(){
        lst=0; ptr=1;
        len[0]=0; len[1]=-1;
        fail[0]=1; fail[1]=1;
    }
    inline int getfail(int x){
        while(x!=1&&(s[n-len[x]-1]==s[n]||n-len[x]-1<1)) x=fail[x];
        return x;
    }
    inline void insert(){
        
        for(n=1;s[n];++n){
            int prt=getfail(lst),c=s[n]-'a';
            if(s[n-len[prt]-1]==s[n]||n-len[prt]-1<1) {lst=0;continue;}
            if(!ch[prt][c]){
                ++ptr,len[ptr]=len[prt]+2;
                int cur=getfail(fail[prt]);
                fail[ptr]=ch[cur][c];
                ch[prt][c]=ptr;
            }
            num[lst=ch[prt][c]]++;
        }
        for(int i=ptr;i>1;--i) num[fail[i]]+=num[i];
        long long ans=0;
        for(int i=2;i<=ptr;++i) ans+=num[i];
        printf("%lld
    ",ans);
    }
    int main(){
        init();
        scanf("%d%s",&n,s+1);
        insert();
        return 0;
    }
    View Code

    K. 对称的正方形

    一道$hash$题,不知道为啥会出在这个专题,不过听到他们报标签就差不多了。

    首先要会二维$hash$,但其实我也是遇到这个题才学会的。

    二维$hash$,就是行列乘上不同的基数然后把一个矩阵变成一个数,建立和查询和二维前缀和一样。

    for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) h1[i][j]=h1[i-1][j]*p1[1]+h1[i][j-1]*p2[1]-h1[i-1][j-1]*p1[1]*p2[1]+a[i][j];
    for(int i=1;i<=n;++i) for(int j=m;j;--j) h2[i][j]=h2[i-1][j]*p1[1]+h2[i][j+1]*p2[1]-h2[i-1][j+1]*p1[1]*p2[1]+a[i][j];
    for(int i=n;i;--i) for(int j=1;j<=m;++j) h3[i][j]=h3[i+1][j]*p1[1]+h3[i][j-1]*p2[1]-h3[i+1][j-1]*p1[1]*p2[1]+a[i][j];
    inline ull hs1(int sx,int sy,int tx,int ty){
        return h1[tx][ty]-h1[tx][sy-1]*p2[ty-sy+1]-h1[sx-1][ty]*p1[tx-sx+1]+h1[sx-1][sy-1]*p1[tx-sx+1]*p2[ty-sy+1];
    }
    inline ull hs2(int sx,int sy,int tx,int ty){
        return h2[tx][sy]-h2[tx][ty+1]*p2[ty-sy+1]-h2[sx-1][sy]*p1[tx-sx+1]+h2[sx-1][ty+1]*p1[tx-sx+1]*p2[ty-sy+1];
    }
    inline ull hs3(int sx,int sy,int tx,int ty){
        return h3[sx][ty]-h3[sx][sy-1]*p2[ty-sy+1]-h3[tx+1][ty]*p1[tx-sx+1]+h3[tx+1][sy-1]*p1[tx-sx+1]*p2[ty-sy+1];
    }

    本题要维护从左上,右上,左下开始的三种$hash$,然后枚举每个点作为矩阵的中心点或中心矩阵的左上角,二分矩阵边长,$check$就用$hash $把矩阵上下,左右判断一下是否相等。

    n,m不相等。

    #include<bits/stdc++.h>
    #define ull unsigned long long
    using namespace std;
    const int N=1005;
    const ull b1=2003081,b2=10035301;
    int n,m;
    ull p1[N],p2[N],h1[N][N],h2[N][N],h3[N][N],a[N][N];
    inline ull hs1(int sx,int sy,int tx,int ty){
        return h1[tx][ty]-h1[tx][sy-1]*p2[ty-sy+1]-h1[sx-1][ty]*p1[tx-sx+1]+h1[sx-1][sy-1]*p1[tx-sx+1]*p2[ty-sy+1];
    }
    inline ull hs2(int sx,int sy,int tx,int ty){
        return h2[tx][sy]-h2[tx][ty+1]*p2[ty-sy+1]-h2[sx-1][sy]*p1[tx-sx+1]+h2[sx-1][ty+1]*p1[tx-sx+1]*p2[ty-sy+1];
    }
    inline ull hs3(int sx,int sy,int tx,int ty){
        return h3[sx][ty]-h3[sx][sy-1]*p2[ty-sy+1]-h3[tx+1][ty]*p1[tx-sx+1]+h3[tx+1][sy-1]*p1[tx-sx+1]*p2[ty-sy+1];
    }
    inline int rd(register int x=0,register char ch=getchar(),register int f=1){
        while(!isdigit(ch)) f=ch=='-'?-1:1,ch=getchar();
        while(isdigit(ch)) x=(x<<1)+(x<<3)+ch-48,ch=getchar();
        return x*f;
    }
    int main(){
        for(int i=p1[0]=p2[0]=1;i<N;++i) p1[i]=p1[i-1]*b1,p2[i]=p2[i-1]*b2;
        n=rd(); m=rd();
        for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) a[i][j]=rd();
        for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) h1[i][j]=h1[i-1][j]*p1[1]+h1[i][j-1]*p2[1]-h1[i-1][j-1]*p1[1]*p2[1]+a[i][j];
        for(int i=1;i<=n;++i) for(int j=m;j;--j) h2[i][j]=h2[i-1][j]*p1[1]+h2[i][j+1]*p2[1]-h2[i-1][j+1]*p1[1]*p2[1]+a[i][j];
        for(int i=n;i;--i) for(int j=1;j<=m;++j) h3[i][j]=h3[i+1][j]*p1[1]+h3[i][j-1]*p2[1]-h3[i+1][j-1]*p1[1]*p2[1]+a[i][j];
        long long ans=0;
        for(int i=1;i<=n;++i) for(int j=1;j<=m;++j){
            int l=0,r=min(min(i-1,n-i),min(j-1,m-j));
            while(l<r){
                int mid=(l+r+1)>>1;
                if(hs1(i-mid,j-mid,i,j+mid)==hs3(i,j-mid,i+mid,j+mid)&&hs1(i-mid,j-mid,i+mid,j)==hs2(i-mid,j,i+mid,j+mid)) l=mid;
                else r=mid-1;
            }
            ans+=l+1;
            if(a[i][j]!=a[i][j+1]||a[i][j]!=a[i+1][j]||a[i+1][j]!=a[i+1][j+1]) continue;
            l=0,r=min(min(i-1,n-i-1),min(j-1,m-j-1));
            while(l<r){
                int mid=(l+r+1)>>1;
                if(hs1(i-mid,j-mid,i,j+mid+1)==hs3(i+1,j-mid,i+mid+1,j+mid+1)&&hs1(i-mid,j-mid,i+mid+1,j)==hs2(i-mid,j+1,i+mid+1,j+mid+1)) l=mid;
                else r=mid-1;
            }
            ans+=l+1;
        }
        printf("%lld
    ",ans);
        return 0;
    }
    View Code
  • 相关阅读:
    git push 报错:missing Change-Id in commit message footer
    script命令录屏
    dubbo.xsd
    常规项目用到的jar包之maven的pom.xml
    WebSocket Demo
    对程序员有帮助的站点集锦
    java之finally的用法
    Java 中的四种引用
    字符串类型的对象与引用及字符串常量池详解
    如何掌握一项新的技能?
  • 原文地址:https://www.cnblogs.com/hzoi2018-xuefeng/p/12101713.html
Copyright © 2020-2023  润新知