「专题总结」回文自动机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 }
View Code

 

最长双回文串:

$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 }
View Code

 

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 }
View Code

 

对称的正方形:

$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 }
View Code

 

顺便提一下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的主要内容,如果未能解决你的问题,请参考以下文章

[专题总结]回文自动机

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

PAM / 回文自动机(回文树)略解

[P5496] 模板回文自动机(PAM)

回文自动机例题

字符串-回文自动机