CF 835D Palindromic characteristics 题解
做法一:字符串哈希
暴力枚举子串,Hash 判断两边是否可行,时间复杂度 $O(n^2 \log n)$。
做法二:DP
这篇文章 写的很清楚。
做法三:PAM
当数据范围 $1 \leq |s| \leq 5000000$ 时,前两种方法是过不去的,能过去也可以被 Hack。所以我们考虑线性的做法,而 PAM 在插入之后上传 $fail$ 树,使 $cnt$ 数组可以 $O(n)$ 求出。
设 $g_p$ 表示长度小于 $\dfrac{len_p}{2}$ 的最长回文后缀,$f_p$ 表示回文串 $\text{p}$ 的阶数,易得以下结论:
$$
\begin{cases}
f_p=1 & \text{ if } \dfrac{len_p}{2} \neq len_{g_{p}} \\
f_p=f_{g_{p}}+1 & \text{ otherwise }
\end{cases}
$$
统计答案时,枚举 $p$,令 $ans_{f_p}+=cnt_p$,最终答案就是对 $ans$ 数组求出后缀和。
namespace LZX
{
using namespace std;
const int MAXN=5000005;
int s[MAXN],n,ch[MAXN][26],idx,fail[MAXN],last,len[MAXN],cnt[MAXN],g[MAXN],f[MAXN];
long long ans[MAXN];
char str[MAXN];
int s_PAM_newnode(int l)
{
len[++idx]=l;
return idx;
}
void s_PAM_clear()
{
idx=-1;
n=last=0;
s_PAM_newnode(0);
s_PAM_newnode(-1);
fail[0]=1;
s[0]=-1;
return;
}
int s_PAM_getfail(int p)
{
while(s[n]^s[n-len[p]-1])
{
p=fail[p];
}
return p;
}
void s_PAM_insert(int awa)
{
int p,c,now;
s[++n]=awa;
p=s_PAM_getfail(last);
if(!ch[p][awa])
{
c=s_PAM_newnode(len[p]+2);
fail[c]=ch[s_PAM_getfail(fail[p])][awa];
if(len[c]<=2)
{
g[c]=fail[c];
}
else
{
now=g[p];
while(s[n]^s[n-len[now]-1]||(len[now]+2)*2>len[c])
{
now=fail[now];
}
g[c]=ch[now][awa];
}
ch[p][awa]=c;
}
last=ch[p][awa];
cnt[last]++;
return;
}
int _main()
{
int l;
scanf("%s",str);
l=strlen(str);
s_PAM_clear();
for(int i=0;i<l;i++)
{
s_PAM_insert(str[i]-'a');
}
for(int i=idx;i>=2;i--)
{
cnt[fail[i]]+=cnt[i];
}
for(int i=2;i<=idx;i++)
{
if(len[i]/2==len[g[i]])
{
f[i]=f[g[i]]+1;
}
else
{
f[i]=1;
}
ans[f[i]]+=1ll*cnt[i];
}
for(int i=l;i;i--)
{
ans[i]+=ans[i+1];
}
for(int i=1;i<=l;i++)
{
printf("%lld ",ans[i]);
}
printf("\n");
return 0;
}
}

浙公网安备 33010602011771号