题目
题目链接:https://www.luogu.com.cn/problem/P5496
给定一个字符串 \(s\)。保证每个字符为小写字母。对于 \(s\) 的每个位置,请求出以该位置结尾的回文子串个数。
这个字符串被进行了加密,除了第一个字符,其他字符都需要通过上一个位置的答案来解密。
具体地,若第 \(i(i\geq 1)\) 个位置的答案是 \(k\),第 \(i+1\) 个字符读入时的 \(\rm ASCII\) 码为 \(c\),则第 \(i+1\) 个字符实际的 \(\rm ASCII\) 码为 \((c-97+k)\bmod 26+97\)。所有字符在加密前后都为小写字母。
\(n\leq 5\times 10^5\)。
思路
推荐文章:OIwiki。
回文自动机可以在线性时间内求出有关字符串某个后缀的后缀回文串等信息。主要处理有关回文的操作。
定义 \(len_x\) 表示点 \(x\) 为回文串最后一个字符的最长回文串长度。\(cnt_x\) 表示以 \(x\) 为回文串最后一个字符时的回文串数量。
类似一般的自动机,PAM 先建立虚点 \(0,1\) 分别表示回文串长度为偶数和奇数的根。其中 \(len_0=0,len_1=-1\),\(fail_0=1\)。
然后依次插入字符串的每一个字符,从最后一个节点开始不断跳 fail,直到某一个点 \(y\) 加入新字符后依然是一个回文串。那么我们就把这个新字符所代表的点接到 \(y\) 上,并更新 \(cnt,len,fail\)。
由于一个字符串中本质不同的回文子串是 \(O(n)\) 级别的,所以时间复杂度为 \(O(n)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=500010;
int n,ans;
char s[N];
struct PAM
{
int tot,last,fail[N],cnt[N],len[N],ch[N][26];
int getfail(int k,int x)
{
while (s[k]!=s[k-len[x]-1]) x=fail[x];
return x;
}
void build()
{
tot=1; last=0;
fail[0]=1; len[1]=-1;
}
void ins(int k,int c)
{
int p=getfail(k,last);
if (!ch[p][c])
{
int np=++tot;
len[np]=len[p]+2;
fail[np]=ch[getfail(k,fail[p])][c];
cnt[np]=cnt[fail[np]]+1;
ch[p][c]=np;
}
last=ch[p][c];
printf("%d ",ans=cnt[last]);
}
}pam;
int main()
{
scanf("%s",s+1);
n=strlen(s+1);
pam.build();
for (int i=1;i<=n;i++)
{
s[i]=(s[i]-97+ans)%26+97;
pam.ins(i,s[i]-'a');
}
return 0;
}