「回文自动机」
前言
处理回文:
- 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; }
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; }
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; }
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; }
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; }