ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

回文自动机(PAM)

2022-07-13 08:34:19  阅读:188  来源: 互联网

标签:int pos len --- tot 自动机 PAM 回文


构造

有两个空根 0 和 1,分别是偶根和奇根。以偶根为根的树存储所有偶数长度的回文串,奇根同理。
因为 Lemma. 「\(s\) 的本质不同回文子串总数是 \(O(n)\)」,所以存得下。

Proof

设以 \(i\) 为尾的最长回文子串左端是 \(l_i\),加入 \(s_{i+1}\) 后,得到了 \(l_{i+1}\)。对于 \(\subseteq [l_{i+1},i+1]\) 的所有以 \(i+1\) 为尾的回文子串,都会在 \([l_{i+1},i+1]\) 的头部已经出现一次。只有 \([l_{i+1},i+1]\) 有可能没出现过。

在回文树中,回文串是怎么存放的呢?奇根上的一条从空根 1 开始的链 1 ---a---> 2 ---b---> 3 ---c---> 4 代表了回文串 cbabc,偶根上的一条从空根上开始的链 1 ---a---> 2 ---b---> 3 ---c---> 4 代表了回文串 cbaabc。这与 AC 自动机直接自根而下存放不同。

回文树中每个节点有 \(fail_x\),表示 \(x\) 代表的回文串的最长共右端点回文子串对应的节点。

如何建树呢?
从左到右加入 \(s\) 中的字符,并用 \(cur\) 来维护当前的 \(s\) 所在的回文树中位置;我们每次的目标是将 \(s+s_i\) 的最长以 \(i\) 为右端回文子串插入回文树中(若还未出现),并将 \(cur\) 变成新插入的节点;以 \(i\) 为右端的其他回文子串已经存在不必插入。

如上图所示,我们先不断跳 \(fail[cur]\) 找到第一个两侧是 \(s_i\)(X)的回文子串,将它对应的节点叫做 \(pos\),接下来不断跳 \(fail[pos]\) 找到第一个(总共是第二个)两侧是 \(s_i\) 的回文子串,将它对应的节点叫做 \(pos'\),将 \(pos,pos'\) 的 s[i]-'a' 子节点分别记作 \(tot\) 和 \(fail[tot]\),其中 \(tot\) 可能需要新建,而 \(fail[tot]\) 无需新建。
接下来要解决的问题是如何判断两侧都是 \(s_i\)。如下图。

所以还需要对每个节点知道它对应回文串的长度 \(len[tot]=len[pos]+2\)。
考虑到当 \(s+s_i\) 的最长以 \(i\) 为右端回文子串只是 \(s_i\) 时无路可跳,我们必须将 \(fail[0]=1,len[1]=-1\) 才能让它正确地挂在了奇根下并获得正确的长度。

char s[N];
int tot=1,pos,cur,trie[N][26],len[N],fail[N];//【tot=1】
int getfail(int x,int i){
	while(i-len[x]-1<0||s[i-len[x]-1]!=s[i])x=fail[x];
	return x;
}
//main
for(int i=0;i<n;i++){
	int b=s[i]-'a';
	pos=getfail(cur,i);
	if(!trie[pos][b]){
		fail[++tot]=trie[getfail(fail[pos],i)][b];
		len[tot]=len[pos]+2;
		trie[pos][b]=tot;
	}
	cnt[trie[pos][b]]++; //利用 cnt[trie[pos][b]]++ 并后面从 fail 树上累加上传还可以统计一个节点的出现次数
	cur=trie[pos][b];
}

练习:Palindrome Characteristics

#include <bits/stdc++.h>
using namespace std;
const int N=5e3+5;//5e6+5 可以通过 |s|=5e6 的加强版
int n,tot=1,pos,cur,len[N],fail[N],trie[N][26],num[N],half[N],cnt[N],buc[N];
char s[N];
vector<int>xr,G[N];
int getfail(int x,int i){
	while(i-len[x]-1<0||s[i-len[x]-1]!=s[i])x=fail[x];
	return x;
}
void dfs(int x,int h){
	xr.push_back(x),half[x]=xr[h];//维护一半回文所在位置
	if(x>1)num[x]=(len[half[x]]==len[x]/2)*num[half[x]]+1;//并递推
	for(int y:G[x]){ 
		int hh=h;
		while(hh+1!=xr.size()&&len[xr[hh+1]]<=len[y]/2)hh++;
		dfs(y,hh);
		cnt[x]+=cnt[y];
	}
	xr.pop_back();
}
int main(){
	scanf("%s",s),n=strlen(s);
	fail[0]=1,len[1]=-1;
	for(int i=0;i<n;i++){
		int b=s[i]-'a';
		pos=getfail(cur,i);
		if(!trie[pos][b]){
			fail[++tot]=trie[getfail(fail[pos],i)][b];
			len[tot]=len[pos]+2;
			trie[pos][b]=tot;
		}
		cnt[trie[pos][b]]++;
		cur=trie[pos][b];
	}
	G[1].push_back(0);
	for(int i=2;i<=tot;i++)G[fail[i]].push_back(i);
	dfs(1,0);
	for(int i=2;i<=tot;i++)buc[num[i]]+=cnt[i];
	for(int i=n-1;i;i--)buc[i]+=buc[i+1];
	for(int i=1;i<=n;i++)printf("%d ",buc[i]);
}

标签:int,pos,len,---,tot,自动机,PAM,回文
来源: https://www.cnblogs.com/impyl/p/16472463.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有