1. 程式人生 > 實用技巧 >[學習筆記]迴文自動機(PAM)

[學習筆記]迴文自動機(PAM)

〇、測試連結

傳送門

壹、定義

其實 \(\tt PAM\) 個人感覺是 \(\tt SAM\)\(\tt AC\) 自動機的組合,用到的大部分思想來自於 \(\tt AC\) 自動機以及 \(\tt kmp\),但是採用的建立方式是 \(\tt SAM\) 的增量法,即來了一個點就在原來的基礎上加入一個點.

對於一個 \(\tt PAM\) 的每個點,有這些是必不可少的東西:

  • \(\tt son[26]\),表示在這個點所代表的字串左右兩端各加上字元後會到哪個點去;
  • \(\tt fail\),類比 \(\tt AC\) 自動機,其含義是這個點所代表的串中的最長迴文字尾(除自己);
  • \(\tt len\)
    ,這個點代表的串的長度;
  • \(\tt sz\),這個點所代表的串出現的次數;

同時,我們還有兩個根,何為其然也?由於我們的 \(\tt son[]\) 的定義,從一個點到子點,都是加上兩個點,但是顯然,迴文嘛,既有奇長度也有偶長度,故而就有奇根(\(0\))與偶根(\(1\)).

同時,由於使用增量法構建,我們還需記錄上一個點的編號 \(\tt lst\).

貳、基礎操作

我們假定 \(\tt s[n]\) 是一個新插入的點,下面給出如何尋找當前的最長迴文字尾.

inline int getfail(int x){
		while(s[n - len[x] - 1] != s[n]) x = fail[x];
		return x;
}

可以類比 \(\tt kmp\) 的過程,不解釋了.

然後就是加入一個新的字元

inline void add(const int x){
	s[++ n] = x;
	int cur = getfail(lst);
	int now = son[cur][x];
	if(!now){
		now = ++ cnt;
		len[now] = len[cur] + 2;
		/** @brief if we start at cur, then we'll find itself
		 *  because cur + x is the definition of point x
		*/
		fail[now] = son[getfail(fail[cur])][x];
		son[cur][x] = now;
		ans[now] = ans[fail[now]] + 1;
	}
	++ sz[now], lst = now;
}

我們首先找到最長的、在加上 \(\tt s[n]\) 之後仍然可以保持迴文的字尾 \(\tt cur\),那麼我們的新點就是 \(\tt cur\)\(\tt son[s[n]]\),但是我們要判斷一下 \(\tt cur\) 是否存在 \(\tt son[s[n]]\),如果不存在,那麼我們新加點,加點時有幾個注意事項:

  1. \(\tt fail[now]=son[getfail(fail[cur])][x]\) 一句中,必須從 \(\tt fail[cur]\) 開始,不然找到的就是 \(\tt now\) 自己;
  2. \(\tt son[cur][x]=now\) 必須放在 \(\tt fail[now]=son[getfail(fail[cur])][x]\) 之後,因為更改了 \(\tt son[cur]\) 之後,可能會對這一句有影響,比如當 \(\tt cur=1\) 的特殊情況.

還要注意的是,如果你的字元的雜湊值從 \(0\) 開始,那麼要將 \(\tt s[0]\) 賦值為字元雜湊值以外的值,否則會因為 \(\tt 'a'=0\) 同時空字元亦為 \(0\)\(\tt WA\) 掉.

叄、程式碼

# include <bits/stdc++.h>
using namespace std;
namespace Elaina{
    # define rep(i,l,r) for(int i=l, i##_end_ = r; i <= i##_end_; ++ i)
    # define fep(i,l,r) for(int i=l, i##_end_ = r; i >= i##_end_; -- i)
    # define fi first
    # define se second
    # define Endl putchar('\n')
    # define writc(x, c) fwrit(x), putchar(c)
    // # define int long long
    typedef long long ll;
    typedef pair<int, int> pii;
    typedef unsigned long long ull;
    typedef unsigned int uint;
    template<class T>inline T Max(const T x, const T y){return x < y ? y : x;}
    template<class T>inline T Min(const T x, const T y){return x < y ? x : y;}
    template<class T>inline T fab(const T x){return x < 0 ? -x : x;}
    template<class T>inline void getMax(T& x, const T y){x = Max(x, y);}
    template<class T>inline void getMin(T& x, const T y){x = Min(x, y);}
    template<class T>T gcd(const T x, const T y){return y ? gcd(y, x % y) : x;}
    template<class T>inline T readin(T x){
        x=0; int f = 0; char c;
        while((c = getchar()) < '0' || '9' < c) if(c == '-') f = 1;
        for(x = (c ^ 48); '0' <= (c = getchar()) && c <= '9'; x = (x << 1) + (x << 3) + (c ^ 48));
        return f ? -x : x;
    }
    template<class T>void fwrit(const T x){
        if(x < 0)return putchar('-'), fwrit(-x);
        if(x > 9)fwrit(x / 10); putchar(x % 10 ^ 48);
    }
}
using namespace Elaina;

const int maxn = 5e5;

char str[maxn + 5]; int lenth;

namespace PAM{
	/** @brief the string*/
	int s[maxn + 5], n;
	/** @brief the answer of each point*/
	int ans[maxn + 5];
	/** @brief the son of each node*/
	int son[maxn + 5][26];
	/** @brief means the longest palindrome of a node(except itself)*/
	int fail[maxn + 5];
	/** @brief the length of a node*/
	int len[maxn + 5];
	/** @brief the number of appearance*/
	int sz[maxn + 5];
	/** @brief the lst node to be insert*/
	int lst;
	/** @brief the count of nodes*/
	int cnt;
	inline int getfail(int x){
		while(s[n - len[x] - 1] != s[n]) x = fail[x];
		return x;
	}
	inline void add(const int x){
		s[++ n] = x;
		int cur = getfail(lst);
		int now = son[cur][x];
		if(!now){
			now = ++ cnt;
			len[now] = len[cur] + 2;
			/** @brief if we start at cur, then we'll find itself
			 *  because cur + x is the definition of point x
			*/
			fail[now] = son[getfail(fail[cur])][x];
			son[cur][x] = now;
			ans[now] = ans[fail[now]] + 1;
		}
		++ sz[now], lst = now;
	}
	inline void build(){
		cnt = lst = 1;
		len[1] = -1, fail[0] = fail[1] = 1;
		s[0] = -1; // the most important, because the hash id is stared at 0, so the empty node should be different from the hash num
		add(str[1] - 'a');
		printf("%d", ans[lst]);
		rep(i, 2, lenth){
			add((ans[lst] - 97 + str[i]) % 26 + 97 - 'a');
			printf(" %d", ans[lst]);
		}
	}
}
using namespace PAM;

inline void init(){
	scanf("%s", str + 1);
	lenth = strlen(str + 1);
}

signed main(){
	init();
	build();// pay attention !!!
	return 0;
}
/*
azyx (aaaa)
1 2 3 4
*/