嘘~ 正在从服务器偷取页面 . . .

洛谷P5496 【模板】回文自动机(PAM) 题解


洛谷P5496 【模板】回文自动机(PAM) 题解

题目链接:P5496 【模板】回文自动机(PAM)

题意

给定一个字符串 $s$。保证每个字符为小写字母。

对于 $s$ 的每个位置,请求出以该位置结尾的回文子串个数。

这个字符串被进行了加密,除了第一个字符,其他字符都需要通过上一个位置的答案来解密。

具体地,若第 $i(i\geq 1)$ 个位置的答案是 $k$,第 $i+1$ 个字符读入时的 $\rm ASCII$ 码为 $c$,则第 $i+1$ 个字符实际的 $\rm ASCII$ 码为 $(c-97+k)\bmod 26+97$。所有字符在加密前后都为小写字母。

输入格式

一行一个字符串 $s$ 表示被加密后的串。

输出格式

一行, $|s|$ 个整数。第 $i$ 个整数表示原串以第 $i$ 个字符结尾的回文子串个数。

数据范围

$1\leq |s|\leq 5\times 10^5$。

本题是 回文自动机 的模板题。

我看题解区的回文自动机都有两个根,看不懂,就自己写了一种回文自动机。

先考虑奇回文串。可以发现,奇回文串去掉两头的字符,就变成了一个新的回文串(单个字符也算)

于是我们可以建一个类似于 Trie 树的东西

根节点 $0$ 还是表示空串,不过根节点的儿子 $\mathtt{ch[0][c]}$ 表示中间的那个字符是 $c$ 。

除此以外,每个节点 $\mathtt{ch[u][c]}$ 表示在父结点 $u$ 的基础上,两头加上字符 $c$ 的字符串。

那么偶回文串怎么办呢?偶回文串其实可以看作中间的那个字符是 $\varnothing$ 。

于是根节点直接多了一个儿子,我们直接把它记为节点 $1$ ,这样回文自动机就只有一个根了。

方便起见,我们记录以每个节点结尾的最长回文串长 $\tt len$ 。定义 $\mathtt{len[0]}=-1,~\mathtt{len[1]} = 0$ 。

$\tt{fail}$ 数组的构建和 AC 自动机类似,定义为最长回文后缀的结尾节点。

定义 $\mathtt{fail[1]} = 0$ ,其他情况 $\mathtt{fail[u]}=\mathtt{ch[fail[fa(u)][c]}$​ 。

这里是因为,$u$ 的最长回文后缀,等于 $fa(u)$ 的最长回文后缀加上字符 $c$

那么我们现在有一个初始有两个节点和 $\mathtt{fail}$ 数组的奇怪数据结构,接下来该干什么呢?

考虑维护一个指针 $p$ ,初始 $p=0$ ,然后依次考虑每个字符 $s_i$ 。

  • 如果 $p$​ 指向的节点有 $s_i$ 这个儿子,那么直接往这个儿子走即可,答案就是这个儿子的答案。

  • 否则新建 $s_i$ 这个儿子,它的 $\tt len$ 定义为 $\mathtt{len[p]}+2$ ,

    接着我们构建它的失配指针 $\mathtt{fail}$ ,即 $\mathtt{ch[p][s_i]}$ 的最长回文后缀(的结尾节点)。

    记一个临时变量 $q\gets p$ ,不停让 $q\gets \mathtt{fail[q]}$ ,

    直到 $q$ 表示的字符串两头加上 $s_i$ 后等于 $\mathtt{ch[p][s_i]}$ 表示的字符串

    于是 $\mathtt{ch[p][s_i]}$ 的 $\mathtt{fail}$ 即为 $\mathtt{ch[q][s_i]}$ 。

然后令 $p=\mathtt{ch[p][s_i]}$ ,继续考虑 $s_i + 1$ 。

答案统计的话,类似于 AC 自动机统计 Fail 树,直接看代码吧。

分析一下复杂度,每增加一个节点,当前深度增加 $1$ 层

而跳 $\mathtt{fail}$ 每次至少减少 $1$ 层,至多减少【当前深度】层。

故时间复杂度 $\mathcal{O}(n)$

代码:

#include <bits/stdc++.h>
using namespace std;
// #define int long long
// #define INF 0x3f3f3f3f3f3f3f3f
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
#define N ((int)(5e5 + 15))

char s[N];
int ans, tot, fail[N], len[N], cnt[N], ch[N][26];
int getfail(int p, int i)
{
    while(s[i - len[p] - 1] != s[i]) p = fail[p];
    return p;
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    cin >> (s + 1); fail[tot = 1] = 0; len[0] = -1;
    for(int i = 1, p, c; s[i]; i++)
    {
        if(i > 1) s[i] = (s[i] + ans - 97) % 26 + 97;
        c = s[i] - 'a'; p = i > 1 ? getfail(p, i) : 0;
        if(!ch[p][c])
        {
            ch[p][c] = ++tot; len[tot] = len[p] + 2; 
            fail[tot] = p > 0 ? ch[getfail(fail[p], i)][c] : 1;
            cnt[tot] = cnt[fail[tot]] + 1; 
        }
        p = ch[p][c]; ans = cnt[p]; cout << ans << ' ';
    }
    return 0;
}

参考文献

[1] https://www.luogu.com.cn/article/ntc6yyfd


题外话

放个图片,很符合我现在的心情。


文章作者: q779
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 q779 !
评论
  目录