博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
回文自动机学习笔记
阅读量:5786 次
发布时间:2019-06-18

本文共 4463 字,大约阅读时间需要 14 分钟。

前言

刚学完manacher就来学回文自动机……

感觉好像(板子)也不是很难(背)

前置知识:Manacher(也不一定非要因为和这个没啥关系),知道自动机是个啥以及怎么建

简述

回文树和回文自动机指的是同一个东西

是由某西伯利亚人于2014夏发明的

这东西主要是用于计数,计算回文串的个数以及种类啥的

建树

图我就不放了(太乱了放了也看不懂),要看图的话可以去这位大神的blog里看一下->

不过个人感觉看文字描述应该就会了……吧……

首先,回文树里有两棵树,分别记录长度为奇数和偶数的回文串

每个节点代表一个回文串,记录转移$x$,表示如果在这个回文串前后都加上字符$x$形成的回文串是子节点的子串

然后每一个节点记录一个fail指针,指向这个回文串的最长后缀回文串

然后我们考虑建树,假设已经建好了串$s[1...i-1]$,要把字符$s[i]$插入这棵树

那么每一次只会把$s[1...i]$的最长后缀回文串加进树里。

证明:(抄的)

我们设后缀回文$i$是最长后缀回文$k$的子串,那么$i$肯定关于$k$的回文中心有一个对称串$j$,由于$k$本身是对称的,所以$j$和$i$是相同的,那么$j$已经被加入到回文树中,所以$i$不必再加入

然后就没问题了。我们设最长回文后缀为$k$,加入字符$c$,那么如果可以,最长回文后缀会变成$ckc$

然而如果$k$之前的字母不是$c$怎么办?这个时候$fail$指针就派上用场了。我们用$fail$维护每一个节点的最长后缀回文,如果$k$不行,我们看看$k$的最长后缀回文是否可行(就是看$k$的最长后缀回文的前一个字母是否等于$c$),然后就这样一直跳$fail$指针直到找到为止(如果一直没有找到会跳到根节点,下面再说)

然后如何维护$fail$呢?我们只要找到了当前节点的最长回文后缀然后记录一下就就好了

然后字符要接在之前的串的后面,记录一下$last$表示上一个串的节点

然后注意特殊处理两个根节点,$0$代表长度为偶数的后缀的根,$1$代表长度为$1$的后缀的根,我们令$fail[0]$指向$1$,$len[1]=-1$,然后令$s[0]=-1$(或任何一个不在原串中出现的字符)($len$代表这个节点的串长)

就比如说如果跳的时候一直找不到回文怎么办?这个时候这个节点就单独形成一个回文串,那么我们在判断$s[i-len[x]-1]==s[i]$的时候,因为$len[1]=-1$,所以必定会停止,那么就不用担心会无限跳下去了

然后来几道题吧

这就是一个板子,顺便记录一下出现次数就好了

然后该有的注解都会写在代码里

1 //minamoto 2 #include
3 #include
4 #define ll long long 5 template
inline bool cmax(T&a,const T&b){
return a

我们肯定要先建出回文自动机的

然后如果是枚举每一个节点暴跳fail指针肯定得T

那么我们对于每一个节点记录一个$trans[i]$,表示小于等于它长度一半的节点

这个可以在建自动机的时候顺便求出来,具体看代码

然后对每一个节点判断长度是否模4为0且$trans[i]$的长度是它的一半就好了

1 //minamoto 2 #include
3 #include
4 template
inline bool cmax(T&a,const T&b){
return a
len[q]) tmp=fail[tmp];27 trans[q]=ch[tmp][x];28 }29 }30 cnt[last=ch[p][x]]++;31 }32 }33 int main(){34 // freopen("testdata.in","r",stdin);35 scanf("%d",&n);36 scanf("%s",s+1);37 s[0]=-1,fail[0]=1,last=0;38 len[0]=0,len[1]=-1,tot=1;39 build();40 for(int i=tot;i>=2;--i) cnt[fail[i]]+=cnt[i];41 for(int i=2;i<=tot;++i)42 if((len[trans[i]]<<1)==len[i]&&len[i]%4==0) cmax(ans,len[i]);43 printf("%d\n",ans);44 return 0;45 }

先建一个回文自动机,然后记$dp[i]$表示转移到$i$节点代表的回文串的最少的需要次数

首先肯定2操作越多越好,经过2操作之后的串必定是一个回文串,所以最后的答案肯定是由一个回文串+不断暴力添加得来,那么答案就是$min(ans,dp[i]+n-len[i])$

然后对于一个串$i$,如果它在前面和后面加上一个字母可以形成回文串$j$,则$dp[j]=dp[i]+1$

为啥嘞?我们可以假设在形成$i$的之前一步把这个字母加上去,执行2操作后就可以变成$j$了

然后我们可以fail指针找到最长的回文串$x$满足$len[x]<=len[i]/2$,那么$dp[i]=min(dp[i],dp[x]+1+len[i]/2-len[x])$(先暴力填好一半,剩下的用2操作)

然后可以用队列记录状态,保证转移至有序的

至于怎么找$x$,我们可以直接在建自动机的时候顺便求出来,就是多跳几次。这个看代码好了

1 //minamoto 2 #include
3 #include
4 template
inline bool cmin(T&a,const T&b){
return a>b?a=b,1:0;} 5 const int N=2e5+5,M=5; 6 char s[N];int dp[N],len[N],fail[N],ch[N][M]; 7 int trans[N],last,p,q,str[N],tot,ans,n,qu[N]; 8 int val[105]; 9 inline int newnode(int x){10 len[++tot]=x;memset(ch[tot],0,sizeof(ch[tot])*5);return tot;11 }12 inline int getfail(int x,int n){13 while(s[n-len[x]-1]!=s[n]) x=fail[x];return x;14 }15 inline void init(){16 val['A']=0,val['T']=1,val['C']=2,val['G']=3;17 s[0]=-1,fail[0]=1,last=0;18 len[0]=0,len[1]=-1,tot=1;19 memset(ch[0],0,sizeof(int)*5),memset(ch[1],0,sizeof(int)*5);20 }21 void ins(int c,int i){22 p=getfail(last,i);23 if(!ch[p][c]){24 q=newnode(len[p]+2);25 fail[q]=ch[getfail(fail[p],i)][c];26 ch[p][c]=q;27 if(len[q]<=2) trans[q]=fail[q];28 else{29 int tmp=trans[p];30 while(s[i-1-len[tmp]]!=s[i]||(len[tmp]+2)*2>len[q]) tmp=fail[tmp];31 trans[q]=ch[tmp][c];32 }33 }34 last=ch[p][c];35 // printf("%d\n",last);36 }37 int main(){38 // freopen("testdata.in","r",stdin);39 int T;scanf("%d",&T);40 while(T--){41 scanf("%s",s+1);42 init(),ans=n=strlen(s+1);43 for(int i=1;i<=n;++i) ins(val[s[i]],i);44 for(int i=2;i<=tot;++i) dp[i]=len[i];45 int h=1,t=0;qu[++t]=0,dp[0]=1;46 while(h<=t){47 int u=qu[h++];48 for(int i=0;i<4;++i){49 int x=ch[u][i];50 if(!x) continue;51 dp[x]=dp[u]+1;52 int y=trans[x];53 cmin(dp[x],dp[y]+1+len[x]/2-len[y]);54 cmin(ans,dp[x]+n-len[x]);55 qu[++t]=x;56 }57 }58 printf("%d\n",ans);59 }60 return 0;61 }

 我感觉我整个人都自动机了……

转载于:https://www.cnblogs.com/bztMinamoto/p/9630617.html

你可能感兴趣的文章
解放双手,基于github travis-ci docker自动化部署java项目
查看>>
聊聊Elasticsearch的CachedSupplier
查看>>
安卓实现局部界面遮罩效果
查看>>
小编带着小白看springboot源码2
查看>>
Zeu.js 1.3.1 发布, 分布式系统可视化
查看>>
对象的生命周期
查看>>
正则中关于环视(lookaround)的小例子
查看>>
《Effective Java》--Java进阶必备
查看>>
Hexo + Github 搭建个人博客
查看>>
Node.js 实现类似于.php,.jsp的服务器页面技术,自动路由
查看>>
用 radial-gradient 实现波浪效果
查看>>
(待补充)CSS进阶--flex布局
查看>>
Xcode统计整个项目代码行数
查看>>
EXCEL破冰 - 如何为透视表组织数据
查看>>
flutter 发布release版的流程(android)
查看>>
Zilliqa的设计构思 第1部分:网络分片
查看>>
promise原理—一步一步实现一个promise
查看>>
PC端编辑 但能在PC端模拟移动端预览的富文本编辑器
查看>>
一些kindle资源
查看>>
android os FileUriExposedException file storage emulated 0 test tx
查看>>