「专题总结」回文自动机PAM
Posted hzoi-deepinc
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了「专题总结」回文自动机PAM相关的知识,希望对你有一定的参考价值。
为了备课,把做完的专题的总结咕了这么久。。。
主要是自己做题做的太慢了,所以讲SAM的时候准备也不充分。
在把讲课时间不断咕之后依然是粗制滥造,锅很多,所以效果很差。而且还有人没听懂。。。
一半人都做了5道题以上了,另一半人还没怎么看,基本所有人都有预习。
得不到任何反馈,也不知道速度如何。就当凑活吧。
挺失败的。可能也没有下一次机会了。
我也不知道后缀数组推荐率是怎么达到100%的。。。那次我讲的自己也很满意
一到难的知识点我就不行了嘛。。。主要是自己理解也很不深刻
至少也还是在3个多小时之内把纯手打的2万个字全都口胡完了。应该不算耽误大家太多时间。
最近状态很差嘛。。。要提高效率了。
扯远了。
回文自动机的思路在于往两边同时加上相同字符。是一种可以在线的数据结构。
len维护回文串的长度,fail指向最长回文后缀。
复杂度为$O(nlogn)$。但是常数较小差不多可以看成大常数$O(n)$
双倍回文:
$Description:$
记字符串x的倒置为$x^R$ 。对字符串x,如果满足$x=x^R$ ,则称之为回文;
如果能够写成$xx^Rxx^R$的形式,则称它是一个「双倍回文」。换句话说,若要x是双倍回文,它的长度必须是$4$的倍数,
而且前半部分后半部分都要是回文。例如:$ABBA$是一个双倍回文,而$ABAABA$不是,因为它的长度不是$4$的倍数。
子串是指在S中连续的一段字符所组成的字符串。
你的任务是,对于给定的字符串,计算它的最长双倍回文子串的长度。$n le 5 imes 10^5$
我的傻逼做法是用$PAM$跑出它的所有子串,然后$hash$判一半是不是回文。
然而好一点的做法是,$PAM$之后在树上$dfs$。如果在$dfs$过程中发现长度恰好为一半的点被经过过,那么就合法。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 500005 4 int f[S],lst,e[27][S],len[S],pc=1,n=1,N,mk[S],ans;char s[S]; 5 #define ull unsigned long long 6 ull Hsh[S],IHsh[S],pw[S]; 7 int fail(int p){while(s[n]^s[n-len[p]-1])p=f[p];return p;} 8 void insert(int c){ 9 int t=fail(lst); 10 if(!e[c][t])f[++pc]=e[c][fail(f[t])],len[e[c][t]=pc]=len[t]+2; 11 lst=e[c][t]; 12 } 13 ull hsh(int l,int r){return Hsh[r]-Hsh[l]*pw[r-l];} 14 ull ihsh(int l,int r){return IHsh[l]-IHsh[r]*pw[r-l];} 15 int main(){ 16 scanf("%d%s",&N,s+1);len[pw[0]=f[0]=f[1]=1]=-1; 17 for(int i=1;i<=N;++i)pw[i]=pw[i-1]*29,Hsh[i]=Hsh[i-1]*29+s[i]-‘a‘+1; 18 for(int i=N;i;--i)IHsh[i]=IHsh[i+1]*29+s[i]-‘a‘+1; 19 #define L len[lst] 20 for(;n<=N;++n)insert(s[n]-96),ans=max(ans,((L&3)==0&&hsh(n-L/4,n)==ihsh(n-L/2+1,n-L/4+1))*L); 21 cout<<ans<<endl; 22 }
最长双回文串:
$Description:$
顺序和逆序读起来完全一样的串叫做回文串。比如acbca是回文串,而abc不是(abc的顺序为“abc”,逆序为“cba”,不相同)。
输入长度为n的串S,求S的最长双回文子串T,即可将T分为两部分X,Y,(|X|,|Y|≥1)且X和Y都是回文串
$2 le |S| le 10^5$
是板子了。正反都做一遍。要注意判断某一侧回文长度为0则不累加答案。
被自己加的测试点$Hack$~
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 500005 4 int pc=1,lst,e[S][26],fail[S],len[S],n=1,E[S],B[S],ans;char s[S]; 5 int Fail(int x,int n){while(s[n]^s[n-len[x]-1])x=fail[x];return x;} 6 void insert(int c,int n){ 7 int t=Fail(lst,n); 8 if(!e[t][c])fail[++pc]=e[Fail(fail[t],n)][c],len[e[t][c]=pc]=len[t]+2; 9 lst=e[t][c]; 10 } 11 int main(){ 12 scanf("%s",s+1);while(s[n])n++;n--; 13 fail[0]=fail[1]=1;len[1]=-1; 14 for(int i=1;i<=n;++i)insert(s[i]-‘a‘,i),B[i]=len[lst]; 15 for(int i=0;i<=pc;++i)fail[i]=len[i]=0; 16 for(int i=0;i<=pc;++i)for(int c=0;c<26;++c)e[i][c]=0; 17 fail[0]=fail[1]=pc=1;len[1]=-1;reverse(s+1,s+1+n); 18 for(int i=1;i<=n;++i)insert(s[i]-‘a‘,i),ans=max(ans,len[lst]&&B[n-i]?len[lst]+B[n-i]:0); 19 cout<<ans<<endl; 20 }
Antisymmetry:
$Description:$
对于一个01字符串,如果将这个字符串0和1取反后,再将整个串反过来和原串一样,就称作“反对称”字符串。比如00001111和010101就是反对称的,1001就不是。
现在给出一个长度为N的01字符串,求它有多少个子串是反对称的。$N le 5 imes 10^5$
题不错。要求稍微改写$PAM$的模板。在相等改为不等的基础上还有一些细节。
长度为奇偶,以及位置0的判断条件。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 500005 4 int fail[S],n,N,e[3][S],dep[S],lst,len[S],pc=1;char s[S];long long ans; 5 int Fail(int p){while((s[n]^s[n-len[p]-1]^1||len[p]&1)&&p^1)p=fail[p];return p;} 6 void insert(int c){ 7 int t=Fail(lst); 8 if(!e[c][t])fail[++pc]=e[c][Fail(fail[t])],len[e[c][t]=pc]=len[t]+2; 9 lst=e[c][t];dep[lst]=dep[fail[lst]]+(len[lst]&1^1); 10 } 11 int main(){ 12 scanf("%d%s",&N,s+1);fail[0]=fail[1]=1;len[1]=-1; 13 for(n=1;n<=N;++n)insert(s[n]-47),ans+=dep[lst]; 14 cout<<ans<<endl; 15 }
对称的正方形:
$Description:$
Orez很喜欢搜集一些神秘的数据,并经常把它们排成一个矩阵进行研究。最近,Orez又得到了一些数据,并已经把它们排成了一个n行m列的矩阵。通过观察,Orez发现这些数据蕴涵了一个奇特的数,就是矩阵中上下对称且左右对称的正方形子矩阵的个数。 Orez自然很想知道这个数是多少,可是矩阵太大,无法去数。只能请你编个程序来计算出这个数。$n,m le 1000$
放错专题。作为一道哈希好题还是不错的。
矩阵哈希也就是个套路,考虑把选中区域左移和上移要乘上什么样的base。
然后就是枚举回文中心,二分半径。
1 #include<iostream> 2 using namespace std; 3 #define ull unsigned long long 4 ull Hsh[3][1111][1111],pw1[1111],pw2[1111];int n,m;long long sum; 5 ull hsh(int o,int u,int d,int l,int r){int c; 6 if(o==1)c=u,u=n+1-d,d=n+1-c; 7 if(o==2)c=l,l=m+1-r,r=m+1-c; 8 u--;l--;int x=r-l,y=d-u; 9 return Hsh[o][d][r]-Hsh[o][d][l]*pw1[x]-Hsh[o][u][r]*pw2[y]+Hsh[o][u][l]*pw1[x]*pw2[y]; 10 } 11 #define M (l+r>>1) 12 int main(){ 13 pw1[0]=pw2[0]=1;cin>>n>>m; 14 for(int i=1;i<=1000;++i)pw1[i]=pw1[i-1]*29,pw2[i]=pw2[i-1]*31; 15 for(int i=1;i<=n;++i)for(int j=1;j<=m;++j) 16 cin>>Hsh[0][i][j],Hsh[1][n+1-i][j]=Hsh[2][i][m+1-j]=Hsh[0][i][j]; 17 for(int o=0;o<3;++o)for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)Hsh[o][i][j]+=Hsh[o][i][j-1]*29; 18 for(int o=0;o<3;++o)for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)Hsh[o][i][j]+=Hsh[o][i-1][j]*31; 19 for(int x=1;x<=n;++x)for(int y=1;y<=m;++y){ 20 int l=1,r=min(min(x,n-x+1),min(y,m+1-y)),ans; 21 #define HSH(O) hsh(O,x-M+1,x+M-1,y-M+1,y+M-1) 22 while(l<=r)if(HSH(0)==HSH(1)&&HSH(0)==HSH(2))l=ans=M,l++;else r=M-1; 23 sum+=ans; 24 #undef HSH 25 } 26 for(int x=1;x<n;++x)for(int y=1;y<m;++y){ 27 int l=0,r=min(min(x,n-x),min(y,m-y)),ans; 28 #define HSH(O) hsh(O,x-M+1,x+M,y-M+1,y+M) 29 while(l<=r)if(HSH(0)==HSH(1)&&HSH(0)==HSH(2))l=ans=M,l++;else r=M-1; 30 sum+=ans; 31 }cout<<sum<<endl; 32 }
顺便提一下manacher吧。
就是大力分类讨论。离线,常数小。复杂度线性。
在相邻字符之间插入特殊字符处理奇偶问题。存储已知回文串的最右端点然后暴力匹配。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define S 22222222 4 char s[S],rs[S];int ans,n=1,R=-1,C,r[S]; 5 int main(){ 6 scanf("%s",rs+1);while(rs[n])n++;n--; 7 for(int i=1;i<=n;++i)s[i*2]=rs[i]; 8 n=n*2+1;s[0]=‘0‘; 9 for(int i=1;i<=n;++i){ 10 if(i<=R)r[i]=min(R-i+1,r[2*C-i]); 11 while(s[i+r[i]]==s[i-r[i]])r[i]++; 12 if(i+r[i]>R)R=i+r[i]-1,C=i;ans=max(ans,r[i]); 13 }printf("%d ",ans-1); 14 }
以上是关于「专题总结」回文自动机PAM的主要内容,如果未能解决你的问题,请参考以下文章