回文自动机(PAM)

回文自动机(PAM)

常用于解决回文串相关的计数问题(可以类比后缀自动机,不过比后缀自动机简单多了)


PAM支持动态插入一个字符,建一课PAM的过程可以看做一个一个字符插入的过程

PAM的建立:

PAM的每个节点代表了一个回文子串,并且PAM每个节点上有四个属性:

\[len[u]:节点u所代表回文子串的长度\\ cnt[u]:节点u所代表回文子串的出现次数\\ ch[u][tp]:节点u在所代表的回文子串在前后各加上一个字符'tp'所对应的下一个字符串\\ fail[u]:节点u所代表的回文子串的一个最长回文后缀所对应的节点\\ \]

初始PAM上有两个点:

\[0:len = 0表示偶数长度的回文子串\\ 1:len = (-1)表示奇数长度的回文子串\\ \]

记上一个插入的位置为\(last节点\),初始\(last = 0 , fail[0] = 1\)

并且显然有,\(last\)构成的fail树是以当前已经插入的字符串为结尾的所有回文子串所对应的节点\((1)\)

下面只考虑插入状态为\((last)\)的平凡情况

类比AC自动机一样,对于当前插入的字符串\(c\),在last对应的\(fail树\)上找一个可以匹配的节点

记当前遍历fail树上的节点\(v\) , 节点\(v\)掌控的区间实际上是\([|S| - 1 - len[v] + 1 , |S| - 1]\)

根据上面(1),很显然可以直接遍历一次fail树就可以处理出当前的fail区间

找到后,指一下儿子过来,处理一下fail就好了

\((lgP3649 [APIO2014]回文串)\)代码:

#include<bits/stdc++.h>
#define MAXN 300005
typedef long long ll;
using namespace std;

int n;
int a[MAXN],last;
char s[MAXN];
int ch[MAXN][29],fail[MAXN],cnt[MAXN];
int tot = (-1),len[MAXN];
ll ans;

int newnode(int x){
	tot++;
	len[tot] = x;
	return tot;
}

int getfail(int x , int y){
	while(a[n - len[x] - 1] != a[y]){
		x = fail[x];
	}
	return x;
}

int main(){
	scanf("%s" , s + 1);
	newnode(0) , newnode(-1);
	fail[0] = 1 , a[0] = (-1);
	int cur,now;
	for(n = 1 ; s[n] ; n++){
		a[n] = s[n] - 'a' + 1;
		cur = getfail(last , n);
		if(!ch[cur][a[n]]){
			now = newnode(len[cur] + 2);
			fail[now] = ch[getfail(fail[cur] , n)][a[n]];
			ch[cur][a[n]] = now;
		}
		last = ch[cur][a[n]];
		cnt[last]++;
	}
	for(int i = tot ; i > 1 ; i--){
		if(fail[i] > 1)cnt[fail[i]] += cnt[i];
		ans = max(ans , 1ll * cnt[i] * len[i]);
	}
	cout<<ans<<endl;
}
posted @ 2021-12-23 21:27  After_rain  阅读(171)  评论(0)    收藏  举报