1. 程式人生 > 其它 >迴文自動機學習筆記

迴文自動機學習筆記

迴文自動機

前言

考試遇到了新科技,所以不得不學一下。

根據各種自動機的姿勢,PAM 是處理迴文問題的有力工具。

OI-Wiki 講的是好的,推薦學習。

PAM 的實用性在於,它用至多 \(O(n)\) 個節點儲存了所有的迴文串,Manacher 與之相比則有很大侷限性。

構樹

Manacher 利用插入 # 的方法避免的迴文串長度奇偶性的討論,因為這個討論真的很麻煩。

而 PAM 的方法為,建立兩個根,分別為奇根和偶根。

每個節點承載的資訊,基本的 PAM 需要記錄 \(len(i),fail(i)\) 分別表示節點 \(i\) 代表的迴文字尾的長度和失配指標。

假設奇根的下標為 \(1\)​​,偶根的下標為 \(0\)

​,那麼 \(len(0)=0,fail(0)=1,len(1)=-1\)​。

而奇根不需要 \(fail\)​ 指標,因為每個字元一定能和​自己形成長度為 \(1\) 的迴文串。

每個 \(fail\)​ 指標實際指向的是自己的最長迴文字尾,這個和大部分自動機是一樣的。

考慮每次加入一個字元,都利用上一次的插入位置,跳 \(fail\) 來匹配迴文串。

考慮在兩頭加字元,這時候奇根的 \(-1\) 就有很好的優勢了,程式碼:

int Node(int l){
	tot ++;
	memset(ch[tot], 0, sizeof(ch[tot]));
	len[tot] = l;
	fail[tot] = 0;
	return tot;
}

void Build(){
	tot = - 1;
	n = las = 0;
	Node(0), Node(-1);
	fail[0] = 1;
}

int Get_Fail(int x){
	while(str[n - len[x] - 1] != str[n]) x = fail[x];
	return x;
}

void Insert(char c){
	str[++ n] = c;
	int now = Get_Fail(las);
	if(!ch[now][c - 'a']){
		int x = Node(len[now] + 2);
		fail[x] = ch[Get_Fail(fail[now])][c - 'a'];
		ch[now][c - 'a'] = x;
	}
	las = ch[now][c - 'a'];
}

性質&應用

可以證明一個字串本質不同的迴文串個數至多為 \(O(|S|)\) 個,所以迴文樹節點個數是 \(O(n)\) 的。

如果需要統計一個字串的本質不同迴文子串個數,那麼就是自動機的狀態數。

而且可以在自動機上 DP 或利用它 DAG 的本質維護些奇奇怪怪的東西,反正都是線性的。

啥你需要模板題

直接從最長字尾開始沿著 \(fail\) 指標遍歷即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

typedef long long LL;
const int N = 3e5 + 10;
int n, tot, las, ch[N][30], len[N], cnt[N], fail[N];
char str[N];

int read(){
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}

int Node(int l){
	tot ++;
	memset(ch[tot], 0, sizeof(ch[tot]));
	len[tot] = l;
	cnt[tot] = fail[tot] = 0;
	return tot;
}

void Build(){
	tot = - 1;
	n = las = 0;
	Node(0), Node(-1);
	fail[0] = 1;
}

int Get_Fail(int x){
	while(str[n - len[x] - 1] != str[n]) x = fail[x];
	return x;
}

void Insert(char c){
	str[++ n] = c;
	int now = Get_Fail(las);
	if(!ch[now][c - 'a']){
		int x = Node(len[now] + 2);
		fail[x] = ch[Get_Fail(fail[now])][c - 'a'];
		ch[now][c - 'a'] = x;
	}
	las = ch[now][c - 'a'];
	cnt[las] ++;
}

int main(){
	scanf("%s", str + 1);
	Build(); int t = strlen(str + 1);
	for(int i = 1; i <= t; i ++) Insert(str[i]);
	LL ans = 0;
	for(int i = tot; i >= 2; i --){
		if(fail[i] > 1)
			cnt[fail[i]] += cnt[i];
		ans = max(ans, 1LL * cnt[i] * len[i]);
	}
	printf("%lld\n", ans);
	return 0;
}