zoukankan      html  css  js  c++  java
  • 回文自动机(PAM)学习&练习笔记

    好久没写博客了喵,其实是我最近太颓了,啥都没干。于是决定学点新东西防颓废。

    学习笔记

    感觉 PAM 对有 SAM 基础的人会很友好,你会发现这个东西比 SAM 好理解多了。

    前置结论

    一个字符串本质不同的回文串是 (O(n)) 级别的。

    设原来有一个字符串 (S) (黑色部分),往后添加了一个字符 (c) (红色部分)。

    我们假设蓝色字符与红色字符之间的字符串回文,且这个是以红色字符结尾的最长的回文串。

    如果存在一个更短的回文串,比如绿色到红色之间的字符串。那么一定有:两个紫色区间的串相等,且都回文。

    也就是说,每新加入一个字符至多产生一个新的本质不同的回文串。

    PAM 的结构

    • 有两个根,一个是奇根,一个是偶根,奇根底下的点表示的是长度为奇数的点,偶根底下表示的是长度为偶数的点。

    • 和 trie 很像,但是每一个节点存的是一个回文串,表示的是从这个节点一直爬到根再回来的这个串。比如到奇根的路径是 abcd ,那么这个节点表示的就是 abcdcba

    建立 PAM

    一般采用增量法构造。

    维护的值

    注意,以下用 fa 表示 trie 树上的父亲。

    • tr[N][S] :trie 上的转移边。

    • fail[N] :指向每一个节点除自己外最长的回文后缀。为什么要维护这个呢?可以看看上面那个前置结论,可能会明白了。

    • len[N] : 每一个节点最长的回文串长度,根据 PAM 形态的定义,显然有 (len(u)=len(fa(u))+2)

    构造方法

    首先建立两个根:奇根,偶根。

    我们考虑把偶根的 (len) 设为 (0) ,奇根的 (len) 设为 (-1) ,偶根的 (fail) 指向奇根。

    (len) 这么开的原因很简单:(len(u)=len(fa(u))+2) ,偶根表示空串,而奇根底下的 (len) 会从 (1) 开始,方便很多。

    (fail) 为什么这么开看完下面就懂了。

    设我们要插入 (S_i)

    (S_{i-1}) 所在的节点为 las,我们通过 las 以及它的 fail 等信息来找出当前插入的字符的位置。

    考虑怎么找这个节点在 trie 上的父亲。

    首先得要保证这个节点回文。

    如果 (S_{x,i}) 回文,那么 (S_{x+1,i-1}) 也回文,所以我们直接遍历 (las) 所有的 fail ,找到最长的一个“合法”的回文串。

    怎样的回文串合法呢?显然是两边都是 (S_i) 这个字符,也就是说,满足 (S_{i-1-len(x)}=S_i) 的节点 (x) 是我们要找的。不满足就跳 (fail)

    会不会永远跳下去呢?这就是奇根 (len=-1) 的妙处了!发现沿着 (fail) 跳,总能跳到奇根上(这个也是偶根指向奇根的原因),因为必定有 (S_{i-1+1}=S_i) ,所以总会停止的。

    于是我们找到了这个节点在 trie 树上的父亲 (f)tr[f][c] 就是这个节点。

    还要维护 (fail)

    因为 (fail) 不能指向自己,相当于指向次短的回文串,那么从 fail[f] 开始往上跳到一个“合法”的节点即可。

    不要忘记更新 (len(u)=len(f)+2) 以及 (las=u),PAM就建完啦!

    时间复杂度

    发现每次 (las) 深度会增加 (1) ,每跳一次深度就会减少 (1) ,所以是线性的!

    另一个有用的信息

    这个做题很常用,trans 指针,指向 (len) 不大于当前节点 (len) 一半的节点。

    维护方法和 (fail) 很像,从 (trans(f)) 开始不断跳 (fail) ,跳到满足 (2*(len(x)+2)le len(u)) 并且 (S_{i-len(x)-1}=S_i) 的节点为止。

    注意这里要加二!因为 trans 指向的应该是 (x) 的孩子。

    还得注意特判 (len(u)le 2) 的情况,这时候直接 (trans(u)=fail(u)) 即可。

    复杂度证明和 (fail) 很像,还是线性的。

    附上我第一代 PAM 板子。根据经验,板子会随时间变化而大型改变。。。

    inline int getfail(int x,int i){
    	while(i-len[x]-1<0||str[i]!=str[i-len[x]-1])x=fail[x];
    	return x;
    }
    inline int gettrans(int x,int i,int lim){
    	while(2*(len[x]+2)>lim||str[i]!=str[i-len[x]-1])x=fail[x];
    	return x;
    }
    void build(char*str,int n){
    	len[0]=0,len[1]=-1,fail[1]=0,tot=1,las=0;
    	for(int i=0;i<n;++i){
    		int c=str[i]-'a',f=getfail(las,i);
    		if(!tr[f][c]){
    			fail[++tot]=tr[getfail(fail[f],i)][c];
    			tr[f][c]=tot;
    			len[tot]=len[f]+2;
    			if(len[tot]<=2)trans[tot]=fail[tot];
    			else trans[tot]=tr[gettrans(trans[f],i,len[tot])][c];
    		}
    		las=tr[f][c];
    	}
    }
    

    练习笔记

    比 SAM 轻松多了!!! 没有大型DS,没有很长的代码,建 PAM 也比建 SAM 小清新的多!

    容易发现,以 (i) 结尾的回文子串数量恰好为它所在节点在 fail 树上的深度,在新建节点的时候顺便维护深度即可。

    找到 (len(trans(u))) 为偶数且 (len(trans(u))*2=len(u))(u) ,取最大的 (len(u)) 即可。

    维护以 (i) 结尾的最长回文串长度 (a_i),反过来再做一遍以 (i) 结尾的最长回文串长度 (b_i),取 (max{a_i+b_{i+1} }) 即可。

    直接建个 PAM, 提取所有 (len) 为奇数的节点,按照 (len) 降序排序。再同时维护每一个串的出现次数,这在 fail 树上打标记即可轻松维护,不会建议重修 ACAM 和 SAM。比 SAM 小清新的是,PAM 节点编号就是拓扑序,不用拓扑排序了。

    刚看完题吓我一跳,如果可以建出 “广义PAM” ,对两个串分别打标记,在 fail 树上跑出每一个节点的回文串在两个串的出现次数,找到都出现的,取 (max{len }) 不就完事了么?后来一想,广义PAM不比广义SAM好建的多?直接 (las) 设为 (0) 再跑一遍就好了,不会像广义SAM一样假掉。

    上一题的双倍经验,改成每一个节点两个串出现次数相乘即可。

    这题在当年应该不简单吧,SAM+倍增+manacher,虽然也不难想。但是PAM出来了之后,这个比模板还模板啊!!!

    好题!
    首先我们发现答案一定是经过一堆以二操作结尾的操作序列,然后不断一操作得到的。
    考虑先建 PAM,设 (dp(u)) 表示变成 (u) 这个节点代表的回文串的最小步数,那么答案就是 (min{dp(u)+n-len(u) })
    考虑怎么转移。我们发现能对答案产生贡献的串的长度一定是偶数(二操作完必然是偶数),所以我们把偶根丢进队列广搜偶树。

    通过一操作转移到这个节点:(dp(u)=dp(fail(u))+1) ,可以考虑它的 (fa) 先在末尾插入这个字符再执行二操作(显然偶树每一个节点最后二操作一下最优)。
    通过二操作转移到这个节点:(dp(u)=dp(trans(u))+1+dfrac{len(u)}{2}-len(trans(u))) 。先用一操作填满到根的路径,再用一次二操作。

    要看我的实现的话,可以到码库里面找~

    路漫漫其修远兮,吾将上下而求索
  • 相关阅读:
    更换惠普G32笔记本的风扇和硬盘,内存条, 谨记 要做好CPU和显卡的 导热硅脂工作!
    怎么更新 WIN10里的SMBv1协议
    ubuntu-12.04.5-desktop-amd64 安装vmwaretools
    如何解决“ VMware Workstation 不可恢复错误: (vcpu-0) vcpu-0:VERIFY vmcore/vmm/main/cpuid.c:386 bugNr=1036521”
    联想移动硬盘无法访问 解决方法1
    阮一峰 ---开发者手册
    Earth Wind 一个查看全球风向的网站
    Linux帮助用法
    Linux历史命令管理以及用法
    Linux操作练习
  • 原文地址:https://www.cnblogs.com/zzctommy/p/14484184.html
Copyright © 2011-2022 走看看