音乐播放器
HMF-have much fun Site
 
文章 标签
16

Powered by Gridea | Theme: Fog
载入天数...
载入时分秒...
总访问量:  |   访问人数:

【学习笔记】回文自动机初步总结

  热度: loading...

什么是PAM啊(战术后仰)

某日看到同学写了道回文自动机题的题解,于是一时兴起学了回文自动机并打了两道题。结果过了几周就忘了,甚至网课上老师提到PAM我以为是后缀自动机。现在想来甚是珂怕,故作此文。

回文自动机(PAM)是一种用于存储字符串回文信息的数据结构。

回文串分为奇回文串和偶回文串,因此我们开两棵树来存储它们,并在后继的描述中将其称作奇树和偶树。

回文自动机的构建

每个回文自动机节点存储着一个值 lenilen_i 表示这个点所代表回文串的长度,设当前节点为 nownow ,那么 trnow,itr_{now,i} 表示在 nownow 节点所代表的回文串的前后各加一个字符 ii 所构成的回文串。显然,从根到 nownow 的链保存了 nownow 所代表的回文串的信息。

fail:failfail : fail 指针存储着该节点所对应的 最长回文后缀 所对应的节点(显然最长回文后缀也一定在这棵树上)。

初始状态下,我们有两个节点,奇树和偶树的根,它们的 lenlen 分别是 1,0-1,0 。其中偶数的根的 failfail 指向奇数的根。

假设我们已经构建完了 1i11 \sim i-1 的回文自动机,考虑加入第 ii 个字符。

  1. 找到 1i11 \sim i-1最长后缀回文串 对应的节点 lstlst (在没有加入字符时 lstlst 默认为偶树的根)。

  2. 尝试在 lstlst 的前后加上字符 ii ,如果 trlst,itr_{lst,i} 已经存在,则将 trlst,itr_{lst,i} 设置为 lstlst ,准备匹配第 i+1i+1 个字符。

  3. 如果 trlst,itr_{lst,i} 为空,那我们需要构建该节点与其 failfail 指针。如果 lstlst 所对应回文串的左端点的前一个字符与 ii 相同,那么新建一个节点 trlst,itr_{lst,i} 表示以 ii 为右端点的最长回文串。否则找到 lstlst 节点的 failfail (其同样是 1i11 \sim i-1 的 后缀回文串),并重复上述操作。直到找到一个后缀回文串能在前后同时加上 ii ,或者找到偶树或奇树的根(代表此时没有已有回文串匹配)。设此时找到的节点为 pp ,新建一个节点 trp,itr_{p,i} 表示以 ii 为右端点的最长回文串,并将 lentrp,ilen_{tr_{p,i}} 设置为 lenp+2len_p +2 (此时将奇数的根的 lenlen 设置为 1-1 的用处体现出来了,奇数的根下面是单个字符,其长度为 11)。

  4. 继续从 pp 往后跳 failfail ,直到找到另一个能在前后同时加上 ii 的节点, 并将在其前后加上 ii 的节点(此处这个节点一定存在,可以尝试根据回文性质自行证明)作为 trp,itr_{p,i}failfail

  5. trp,itr_{p,i} 设置为 lstlst ,准备匹配第 i+1i+1 个字符。

例题:

P4762 [CERC2014]Virus synthesis


利用回文自动机查询每个回文串 长度在其一半及以下 的后缀回文串并dp即可。


CodeCode

#include<bits/stdc++.h>
#define N 200010
#define sz sizeof(int)*5
using namespace std;
char s[N];
int T,n,val[105],tot;
int dp[N],len[N],fail[N],ch[N][5];
int trans[N],last,p,q,str[N],ans,que[N];
void init() {
	val['A']=0,val['C']=1,val['G']=2,val['T']=3;
	s[0]=-1,fail[0]=1,last=0;
	len[0]=0,len[1]=-1,tot=1;
	memset(ch[0],0,sz),memset(ch[1],0,sz);
}
int nw(int x) {
	len[++tot]=x;
	memset(ch[tot],0,sz);
	return tot;
}
int getfail(int x,int n) {
	while(s[n-len[x]-1]!=s[n]) x=fail[x];
	return x;
}
void ins(int c,int i) {
	p=getfail(last,i);
	if(!ch[p][c]) {
		q=nw(len[p]+2);
		fail[q]=ch[getfail(fail[p],i)][c];
		ch[p][c]=q;
		if(len[q]<=2) trans[q]=fail[q];
		else {
			int tmp=trans[p];
			while(s[i-1-len[tmp]]!=s[i]||(len[tmp]+2)*2>len[q]) tmp=fail[tmp];
			trans[q]=ch[tmp][c];
		}
	}
	last=ch[p][c];
}
int main()
{
	scanf("%d",&T);
	while(T--) {
		scanf("%s",s+1);
		init();
		ans=n=strlen(s+1);
		for(int i=1; i<=n; i++) ins(val[s[i]],i);
		for(int i=2; i<=tot; i++) dp[i]=len[i];
		int head=1,tail=0;que[++tail]=0,dp[0]=1;
		while(head<=tail) {
			int now=que[head++];
			for(int i=0; i<4; i++) {
				int x=ch[now][i];
				if(!x) continue;
				dp[x]=dp[now]+1;
				int y=trans[x];
				dp[x]=min(dp[x],dp[y]+1+len[x]/2-len[y]);
				ans=min(ans,dp[x]+n-len[x]);
				que[++tail]=x;
			}
		} 
		printf("%d\n",ans);
	}
	return 0;
 } 

P4555 [国家集训队]最长双回文串


从正序和倒序建两个回文自动机,记录以 ii 为右端点的最长后缀回文串和以 ii 为左端点的最长前缀回文串,枚举分割点即可。


CodeCode

//2018.11.21 by ljz
#include<bits/stdc++.h>
using namespace std;
#define res register int
#define LL long long
#define inf 0x3f3f3f3f
#define eps 1e-15
inline int read(){
    res s=0;
    bool w=0;
    char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')w=1;ch=getchar();}
    while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
    return w?-s:s;
}
inline void _swap(res &x,res &y){
    x^=y^=x^=y;
}
inline int _abs(const res &x){
    return x>0?x:-x;
}
inline int _max(const res &x,const res &y){
    return x>y?x:y;
}
inline int _min(const res &x,const res &y){
    return x<y?x:y;
}
const int N=1e5+10;
namespace MAIN{
    int n;
    int a[N],b[N];
    struct PAM{
        struct Pam{
            int vis[26],len,fail;
        }pam[N];
        int las,cnt;
        PAM() {pam[1].fail=pam[0].fail=1,pam[cnt=1].len=-1;}
        inline void extend(const res &x,const res &id,char *str){
            res p=las;
            for(;str[id-pam[p].len-1]!=str[id];p=pam[p].fail);
            if(!pam[p].vis[x]){
                res np=++cnt,q=pam[p].fail;
                for(;str[id-pam[q].len-1]!=str[id];q=pam[q].fail);
                pam[np].fail=pam[q].vis[x],pam[p].vis[x]=np,pam[np].len=pam[p].len+2;
            }
            las=pam[p].vis[x];
        }
    }A,B;
    char str[N];
    int ans;
    inline void MAIN(){
        scanf("%s",str+1);
        n=strlen(str+1);
        for(res i=1;i<=n;i++)A.extend(str[i]-'a',i,str),a[i]=A.pam[A.las].len;
        reverse(str+1,str+n+1);
        for(res i=1;i<=n;i++)B.extend(str[i]-'a',i,str),b[n-i+1]=B.pam[B.las].len;
        for(res i=1;i<n;i++)ans=_max(a[i]+b[i+1],ans);
        printf("%d\n",ans);
    }
}
int main(){
    MAIN::MAIN();
    return 0;
}