[学习笔记]回文自动机(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
*/
posted @ 2021-01-10 15:57  Arextre  阅读(94)  评论(0编辑  收藏  举报