【模板】回文字符机 PAM

【模板】回文自动机 PAM

回文自动机

定义

回文自动机(Palindrome Automaton)是处理回文问题的利器。类似后缀自动机,回文自动机有:

  • 状态:每个回文子串是一个状态,没有两个状态代表相同的回文子串。
  • 转移:从一个状态出发有转移边,字符 \(r\) 的转移边表示在两边加上 \(r\) 所对应的回文子串,或者没有。
  • Fail 指针:Fail 指针指向这个回文子串的一个后缀,满足它也是回文子串。

引理

对于一个回文串 \(s\),它有一段后缀 \(t\),满足:

  • 如果 \(t\) 是 border,那么 \(t\) 是回文串(正着读和反着读确实一样)
  • 如果 \(t\) 是回文串,那么 \(t\) 是 border(因为正着读和反着读一样)

以下是非常形象的证明:

所以我们的 fail 指针实际上指的是最长 border。

构建

先说怎么构建再谈正确性:首先我们需要一个偶回文根 \(0\) 和奇回文根 \(1\)(都是空的),并使得 \(fail_0=1\)。记录 \(len_u\) 表示这个状态对应的回文子串的长度,\(len_1=-1\)。欲将插入字符 \(s_i\),要从上次插入的地方 \(last\) 开始,我们要找到:谁的转移边应该到 \(u\)\(u\) 的 fail 指针指向谁?

  1. 我们沿着 fail 树往上跳,跳到第一个满足:\(s[i-len[p]-1]=s[i]\)\(p\),那么这个 \(p\) 的转移边 \(ch_{p,r}\) 应该就是我们新的回文子串。
  2. 如果 \(ch_{p,r}\) 已存在,算法结束,返回 \(last=ch_{p,r}\);否则新建点 \(ch_{p,r}:=u\)
  3. \(len_u=len_p+2\)(由 \(len\) 定义)
  4. \(fail_u\) 的求值需要再跳一次 fail,找到另一个满足那个条件的,\(fail_u\) 就是它的 \(r\) 的转移边。
  5. \(last=u\)。结束。

观察到这个算法为什么没有更新其它的转移边,原因很简单,因为已经有了。可以理解一下,\(p\) 前面有一个 \(r\),往上跳到一个 \(q\) 前面也有 \(r\) 的话,根据 \(p\) 的回文,\(q\) 的在左面的映射的后面有 \(r\),同时前面有 \(r\),那么说明 \(q\)\(r\) 的转移边已经存在,这个回文串已经被创建。这同时证明了一个串的回文子串最多有 \(O(n)\) 个,上界在 aa...a 取得。

2

code

template<int N,int M> struct palindam{
	int s[N+10],cnt,ch[N+10][M],fail[N+10],len[N+10],tot;
	int trans[N+10];
	palindam():cnt(0),tot(1){fail[0]=1,len[1]=-1;}
	bool diff(int p){int i=cnt-len[p]-1; return i<0||s[i]!=s[cnt];}
	int getfail(int p){while(diff(p)) p=fail[p]; return p;}
	int expand(int p,int r){
		s[++cnt]=r,p=getfail(p);
		if(ch[p][r]) return ch[p][r];
		int u=++tot; len[u]=len[p]+2;
		fail[u]=ch[getfail(fail[p])][r];
		return ch[p][r]=u;
	}
};
template <int N, int M>
struct palindam {
  int str[N + 10], cnt, ch[N + 10][M], fail[N + 10], len[N + 10], dep[N + 10], tot;
  palindam() : cnt(0), tot(1) { 
    memset(ch[0], 0, sizeof ch[0]);
    memset(ch[1], 0, sizeof ch[1]);
    fail[0] = 1;
    len[1] = -1;    str[0] = -1;
  }
  int getfail(int p) {
    while (cnt - len[p] - 1 < 1 || str[cnt - len[p] - 1] != str[cnt])
      p = fail[p];
    return p;
  }
  int expand(int p, int r) {
    str[++cnt] = r;
    p = getfail(p);
    if (ch[p][r]) return ch[p][r];
    int u = ++tot;
    memset(ch[u], 0, sizeof ch[u]);
    len[u] = len[p] + 2;
    fail[u] = ch[getfail(fail[p])][r];
    dep[u] = dep[fail[u]] + 1;
    ch[p][r] = u;
    return u;
  }
};
// 插入从 lst = 1 或 lst = 0 开始都对

应用

双回文子串

形如 \(ww^Rww^R\) 的字符串是双回文子串,其中 \(w\) 是正串,\(w^R\) 是反串。这是一个回文串,回文部分是回文串,长度是 \(4\) 的倍数。

我们枚举每个回文子串,那么我们就要找到他的 fail 祖先中刚好等于它的长度一半的 border。当然可以树上倍增,但我们可以维护一个指针 \(trans_u\) 表示 \(u\) 的祖先中 \(\leq len_u/2\) 的一个 border,枚举的时候查验一下是不是等于即可。在新建点的时候维护 \(trans\),首先如果 \(len_u\leq 2\) 那么取它的父亲即可,否则从 \(trans_p\) 开始向上跳,设跳到 \(q\),则 \(len_q+2\leq len_u/2\) 时且合法时停下(1. 这相当于跳 \(fail_u\) 2. 常数次跳跃),然后取 \(trans_u=ch_{q,r}\) 即可。

if(len[u]<=2) trans[u]=fail[u];
else{
	int q=trans[p];
	while(diff(q)||(len[q]+2)<<1>len[u]) q=fail[q];
	trans[u]=ch[q][r];
}

回文划分

https://oi-wiki.org/string/pam/

这里抛出一些结论:

  • Weak periodicity lemma:若 \(p,q\) 都是字符串 \(s\) 的 period,\(p+q\leq |s|+1\),则 \(\gcd(p,q)\) 也是 period。
  • 所以,一个串的所有 border 构成 \(O(\log n)\) 个等差数列。

要做回文划分,就是 \(dp_i=1+\min_{j}f_j\) 其中 \([j-1,i]\) 回文,那么这个回文就是每个点插入的时候返回的点和他们的 fail,根据刚才的结论优化 DP 即可。这部分,OI wiki 可参考,这部分内容的完善已加入 todo list

广义 PAM

其实没有改多少。但是注意,PAM 复杂度分析基于当前点在 fail 树上深度(每次加入字符深度至多 +1),所以有可能还不能对 Trie 上建 PAM。

点击查看代码
template <int N>
struct palindam {
  unordered_map<int, int> ch[N + 10];
  int fail[N + 10], len[N + 10], dif[N + 10], tot, trans[N + 10];
  palindam() : tot(1) { fail[0] = 1, len[1] = -1; }
  int getfail(int p, char* s, int rlen) {
    while (len[p] + 1 >= rlen || s[0] != s[-len[p] - 1]) p = fail[p];
    return p;
  }
  int expand(int p, int r, char* s, int rlen) {
    p = getfail(p, s, rlen);
    if (ch[p][r]) return ch[p][r];
    int u = ++tot;
    len[u] = len[p] + 2;
    fail[u] = ch[getfail(fail[p], s, rlen)][r];
    dif[u] = len[u] - len[fail[u]];
    return ch[p][r] = u;
  }
}
  for (int i = 1; i <= n; i++) {
    string s;
    cin >> s;
    for (int j = 0, lst = 1; j < (int)s.size(); j++)
      lst = M.expand(lst, s[j] - 'a', s.data() + j, j + 1);
  }
posted @ 2023-08-01 08:26  caijianhong  阅读(15)  评论(0编辑  收藏  举报