struct PAM_Trie
{
    int ch[30]; //字典树
    int fail; //fail指针,指向当前节点所表示的回文串的最长回文后缀(不包括自己)
    int len;  //len表示当前节点表示的回文串的长度
    int num;    //以第i个字符结尾的回文串的个数
};
struct PAM
{
    PAM_Trie b[maxn];   //字典树
    int len_str;      //字符串的长度
    int last;         //上一次插入的节点编号
    int cnt;          //总的节点的个数
    int s[maxn];          //原字符串对应的ASCII码
    char c[maxn];     //原字符串
}回文自动机和\(trie\)树一样,将信息存储在边上。
回文自动机的每一个节点(除了根)都表示一个回文串,一个节点向下连一条边\(ch\)代表在他两边各自加一个字符,即\(len+=2\)。
如图所示加深理解:

如图所示,他每个节点所代表的回文串分别是(从\(2\)到\(5\)):\(aa,a,b,aba\)。(这里这张图只是增加感性认识,真实构建可能不会出现此形态的自动机。)
因为\(1\)号树是存长度为奇数的回文串,每次操作又要\(len+=2\),为了减少特判,相信这里大家也能明白这里为什么\(len(1)\)的初值为\(-1\)。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 10;
struct PAM_Trie
{
    int ch[30]; //字典树
    int fail;   //fail指针,指向当前节点所表示的回文串的最长回文后缀(不包括自己)
    int len;    //len表示当前节点表示的回文串的长度
    int num;    //以第i个字符结尾的回文串的个数
};
struct PAM
{
    PAM_Trie b[maxn];   //字典树
    int len_str;        //字符串的长度
    int last;           //上一次插入的节点编号
    int cnt;            //总的节点的个数
    int s[maxn];        //原字符串对应的ASCII码
    char c[maxn];       //原字符串
    PAM() //构造函数自动调用
    {
        b[0].len = 0;  //初始状态下长度为0
        b[1].len = -1; //1号节点len=-1方便接下来的操作
        b[0].fail = 1; //0号节点fail指向1号节点
        b[1].fail = 0; //1号节点fail指向0号节点
        last = 0;      //last初始值为0
        cnt = 1;       //cnt=1是因为初始的时候有1号结点
                       //插入的时候可用++cnt
    }
    //读入字符串
    void read()
    {
        scanf("%s", c + 1);
        len_str = strlen(c + 1);
    }
    //寻找当前
    int get_fail(int las, int i)
    {
        //假如说我刚进来 
        //las是上一次的插入的节点
        //我们又知道len(las)代表以las这个点的回文串
        //所以这是一段中间是回文串的字符串
        //我们只需要验证两端,也就是i-len(las)-1和i如果相等
        //则说明i这个点的最长回文串长度是len(las)+2
        //不匹配的话,las就跳转到fail指针指向的节点接着找
        while(s[i-b[las].len-1] != s[i]) 
            las = b[las].fail;
        return las;
    }
    //新建节点
    void ins(int i)
    {
        int p = get_fail(last, i);
        //找当前节点两端能匹配的那个位置
        if(!b[p].ch[s[i]]) //如果当前trie图里没有这个节点
        {
            //新节点的长度是前后各自拼接了一个字符
            //所以当前节点的长度比原来多2
            b[++cnt].len = b[p].len + 2;
            
            //为当前位置寻找fail指针
            int tmp = get_fail(b[p].fail, i);
            b[cnt].fail = b[tmp].ch[s[i]];
            
            //更新答案 同时给trie图中的该节点赋值
            b[cnt].num = b[b[cnt].fail].num + 1;
            b[p].ch[s[i]] = cnt;
        }
        //更新last(上一次插入的节点)
        last = b[p].ch[s[i]];
    }
    //这题题目要求第i个位置答案是k,第i+1个位置代表的
    //字符就变成了(c-97+k) % 26 + 97
    void solve()
    {
        int k = 0;
        s[0] = int('#'); //这里写啥都好, 只要input里不会出现就行
        for(int i = 1; i <= len_str; i++)
        {
            c[i] = (c[i] - 97 + k) % 26 + 97;
            s[i] = c[i] - 'a';
            ins(i);  //在自动机中插入结点
            printf("%d ", b[last].num);
            //输出以当前字符结尾的回文串数量
            k = b[last].num; //跟随题意
        }
    }
}P;
int main()
{
    P.read();
    P.solve();
    return 0;
}
原文:https://www.cnblogs.com/zxytxdy/p/11626213.html