「回文自动机」

前言

处理回文:

  • 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\n",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\n",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\n",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]?' ':'\n'),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\n",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\n",ans);
    return 0;
}
View Code
posted @ 2019-12-26 19:08  _xuefeng  阅读(206)  评论(2编辑  收藏  举报