「专题总结」回文自动机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 \times 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 \times 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\n",ans-1);
14 }

洛谷模板

原文地址:https://www.cnblogs.com/hzoi-DeepinC/p/12103616.html

时间: 2024-11-09 03:47:00

「专题总结」回文自动机PAM的相关文章

「不会」回文算法

什么回文算法,我只会背两个板. 「双倍回文」 利用pam的fail树定义:一个节点的fail是他的最长回文后缀 那么在这棵树上dfs,记录沿路经过了哪些长度 那么到达长度为len的回文节点时,如果 len%4==0&&vis[len/2] 则作出贡献 「最长双回文串」 两个回文串拼起来的方案数,可以manacher 「I Love Palindrome String 」 reverse一下,那么问题变成长为len同时长为len/2的后缀也为回文串的数量 还是fail树 「Antisymme

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

蒟蒻开始学回文自动机了 (板子基本靠搬运) #include <bits/stdc++.h> using namespace std; const int N = 2e6 + 5; struct PAM_Trie { int ch[26]; int fail, len, num; }; struct PAM { PAM_Trie b[N]; int n, last, cnt, s[N]; PAM() { b[0].len = 0; b[1].len = -1; b[0].fail = 1; b

【回文自动机】bzoj3676 [Apio2014]回文串

回文自动机讲解!http://blog.csdn.net/u013368721/article/details/42100363 pam上每个点代表本质不同的回文子串.len(i)代表长度,cnt(i)代表个数(要最后在fail树上dp一遍方可). 答案直接枚举一遍结点,然后用len(i)*cnt(i),取最大者即可. 回文自动机是非常优越的数据结构,可惜比manacher多一个字符集的空间-- #include<cstdio> #include<cstring> #include

【bzoj3676】[Apio2014]回文串 回文自动机

题目描述 考虑一个只包含小写拉丁字母的字符串s.我们定义s的一个子串t的“出现值”为t在s中的出现次数乘以t的长度.请你求出s的所有回文子串中的最大出现值. 输入 输入只有一行,为一个只包含小写字母(a -z)的非空字符串s. 输出 输出一个整数,为逝查回文子串的最大出现值. 样例输入 [样例输入l] abacaba [样例输入2] www 样例输出 [样例输出l] 7 [样例输出2] 4 题解 回文自动机裸题 关于PAM个人暂时理解不是很深入,挖坑待填. 本题只需要统计fail树的子树大小,再

【XSY2715】回文串 树链剖分 回文自动机

题目描述 有一个字符串\(s\),长度为\(n\).有\(m\)个操作: \(addl ~c\):在\(s\)左边加上一个字符\(c\) \(addr~c\):在\(s\)右边加上一个字符 \(transl~l_1~r_1~l_2~r_2\):有两个\(s\)的子串\(s_1=s[l_1\ldots r_1],s_2=s[l_2\ldots r_2]\).你要把\(s_1\)变成\(s_2\).每次允许在左边加一个字符或删一个字符.要求操作次数最少.定义一个字符串是好的当且仅当这个字符串是回文串

回文自动机例题

今天学习了一下回文自动机,吊打\(manacher\)有没有(除了空间) 回文自动机基于这两个性质: 1.一个长度为\(n\)的字符串的本质不同回文子串是\(O(n)\)级别的 2.在一个字符串后增加一个字符后,最多新增\(1\)个本质不同回文子串 这两条性质都可以用归纳法证明 于是我们想到用一个结点来代表一个本质不同的回文串,然后就有\(PAM\)了.和\(AC\)自动机一样,\(PAM\)也有\(fail\)失配边和\(ch\)转移边,\(fail\)代表的是最长回文后缀 采用增量构造法来构

回文自动机入门题

URAL-1960.Palindromes and Super Abilities 传送门 •题意 给你一个长度为 n 的字符串 s,下标从 1 开始: 输出 n 个数,第 i 个数表示 1~i 内有多少个本质不同的回文串: •题解 回文自动机入门题: 定义 ans[ i ] 表示 1~i 共有 $ans_{i}$ 个本质不同的回文串: $ans_{i}=ans_{i-1}$+{第 i 个字符可形成本质不同的回文串 ? 1:0}; •Code 1 #include<bits/stdc++.h>

P3649 [APIO2014]回文串(回文自动机)

回文自动机裸题,把PAM建出来以后对每个节点更新答案即可 代码: #include <bits/stdc++.h> #define int long long #define sc(a) scanf("%lld",&a) #define scc(a,b) scanf("%lld %lld",&a,&b) #define sccc(a,b,c) scanf("%lld %lld %lld",&a,&

回文自动机刷题总结

最长双回文串 裸的回文自动机,将串reverse再插入一遍即可. 双倍回文 这题可以只维护偶回文串然后疯狂加特判判掉奇串 回文自动机,再多维护一个trans指针,指向trans[x]表示长度小于len[x]/2的最长的回文后缀 trans指针可以从父亲(不是fail)的trans指针求出. 其实还可以直接建完自动机后在fail树(即把fail指针当作父亲边构成的树)上开桶dfs 1 #include<bits/stdc++.h> 2 #define N 500050 3 using names