Loading

PAM 学习笔记

说在前头

回文自动机又叫回文树,缩写 PAM。它可以处理和回文子串有关的问题。在 PAM 上,一个点存储的是一个 回文子串

  • 一些约定:
    • \(\text{len[u]}\) 表示 \(u\) 代表的回文子串长度。可以发现 \(\text{len[ch[u][x]] = len[u] + 2}\)
    • \(\text{ch[u][c]}\) 表示在 \(u\) 两端添加字符 \(c\) 后变成的回文子串。
    • \(\text{fail[u]}\) 表示 \(u\) 的最长回文真后缀。

构造 PAM

构造 PAM 时,我们采取了 增量转移 的方式。简单地说,就是把字符串 \(S\) 从左往右扫一遍,每加入一个新的字符 \(c\) 就更新至多一个节点(也可能不更新)。

假设当前处理第 \(i\) 个字符。令指针 \(\text{last}\) 表示第 \(i-1\) 个字符处理时最后停在哪个节点上,这个节点同时代表着结尾为前 \(i-1\) 个字符的最长回文后缀。

可以证明,每增加一个字符 \(c\),至多只会增加一个本质不同的回文串。

于是我们让 \(\text{last}\) 沿着 \(\text{fail}\) 跳,直到碰到一个回文后缀 \(p\) 满足其开头 \(-1\) 的位置等于这个 \(i\) 的位置,此时从开头 \(-1\) 的位置到 \(i\) 的位置构成了以 \(i\) 结尾的最长回文后缀。

如果这个以 \(i\) 结尾的最长回文后缀还没有出现过,我们就新建一个,在程序里体现为 \(\text{ch[p][c]=++tot}\)。否则就不用。最后再把 \(\text{last}\) 指向 \(\text{ch[p][c]}\)

至于求 \(\text{fail}\) 和上一步类似。我们只需要再找到这样一个回文后缀 \(p\) 的基础上,令 \(p=\text{fail[p]}\),依然是跳到满足「回文后缀 \(p\) 开头 \(-1\) 的位置等于这个 \(i\) 的位置」时停下。

初始化

我们建两棵树,将 \(len\) 为奇数和 \(len\) 为偶数的结点分开。定义奇根为 \(1\),偶根为 \(0\)

  • 方便起见,定义 \(\text{len[1]=-1,len[0]=0}\),其意义显然。
  • 由于奇根不可能失配,所以 \(\text{fail[1]}\) 的值无所谓。而偶根 \(\text{fail}\) 则指向奇根。
  • 考虑在 PAM 中加入一个字符 \(c\),使得其最长回文后缀是它本身。它的 \(\text{fail}\) 显然可为 \(0\),可为 \(1\)。代码实际实现中,其 \(\text{fail}\)\(0\),但由于偶根 \(\text{fail}\) 指向奇根,因此即使偶根失配还有奇根在后面顶着,符合要求。

代码

char S[Maxn];
int n, ans, num[Maxn], len[Maxn], fail[Maxn], tr[Maxn][27], last, tot;

void insert(int ch, int i) {
    int p = last;
    while(S[i] != S[i - len[p] - 1]) p = fail[p];
    if(!tr[p][ch]) {
        ++tot; int t = fail[p];
        while(S[i] != S[i - len[t] - 1]) t = fail[t];
        fail[tot] = tr[t][ch];
        len[tot] = len[p] + 2;
        num[tot] = num[fail[tot]] + 1;
        tr[p][ch] = tot;
        // cout << tot << " " << fail[tot] << endl;
    }
    last = tr[p][ch];
}

void init() {
    len[1] = -1, len[0] = 0; // 1 -> 奇根 ; 0 -> 偶根
    fail[0] = fail[1] = 1;
    last = 1; tot = 1;
}

int main() {
    scanf("%s", S + 1);
    n = strlen(S + 1);
    init();
    for(int i = 1; i <= n; ++i) {
        if(i > 1) S[i] = (S[i] - 97 + ans) % 26 + 97;
        insert(S[i] - 'a' + 1, i);
        ans = num[last];
        printf("%d ", ans);
    }
    return 0;
}

以上程序为 Luogu 上的模板题 :P5496 [模板] 回文自动机 (PAM)

posted @ 2020-08-18 23:30  Sqrtyz  阅读(132)  评论(0)    收藏  举报