回文套娃计数
|Word count:711|Reading time:3min|Post View:
题解
给定一个长度为$n(n \leq 10^6)$的字符串$S$,求其中的回文套娃个数。回文套娃定义为两个序列$l_1<l_2<…<l_k$和$r_1>r_2>…>r_k$,其中每个$S[l_i:r_i]$均为回文串。
在回文树中,每个节点$x$代表的字符串有两个前驱,第一个是删除第一个和最后一个字符得到的父亲节点$fa(x)$,第二个是最长前/后缀所代表的节点$f(x)$。
对于每个回文串$s[l:r]$,其包含的回文子串$s[x:y]$都可以通过如下的方式不重不漏地访问:先跳若干步$fa$,直到$l==x$或$r==y$。此时$s[x:y]$一定是$s[l:r]$的前缀或者后缀,所以再连续跳若干步最长前缀或者跳若干步最长后缀即可(此步骤中不能既跳前缀又跳后缀,否则将不满足$l==x$或$r==y$,此时得到的串将会被重复访问)。
我们设$ff(x)$表示一定选节点$x$所代表的字符串,且该字符串是套娃的最外层的方案数。那么枚举其包含的每个回文子串$y$,可得到$ff(x)=(\sum ff(y))+1$。简化方法即分别记录两种前驱的前缀和进行运算,详见以下代码。最后枚举右端点,将当前右端点所对应的所有回文串作为最外层套娃的方案数计入答案中即可。
代码
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1e6+5; const ll mod = 998244353;
char s[N]; int n; int ans;
struct PalindromesTree{ int tot,last,l[N],son[N][26],c[N],f[N]; ll ff[N]; ll pref[N],prefa[N];
int newnode(int x){ l[++tot]=x; return tot; } void init(){ tot=-1;last=0; f[newnode(0)]=1; f[newnode(-1)]=0; } int get(int x,int n){ while(s[n]!=s[n-l[x]-1])x=f[x]; return x; } void add(int x,int i){ int y=get(last,i); if(!son[y][x]){ int now=newnode(l[y]+2),ne=get(f[y],i); f[now]=son[ne][x]; son[y][x]=now; ff[now]=(prefa[y]+1)%mod; pref[now]=(pref[f[now]]+ff[now])%mod; prefa[now]=(prefa[y]+2*pref[f[now]]+ff[now])%mod; } last=son[y][x]; (ans+=pref[son[y][x]])%=mod; } void solve(){ for(int i=1;i<=n;++i)add(s[i]-'a',i); } }T;
int main(){ scanf("%s",s+1); n=strlen(s+1); T.init(); T.solve(); printf("%d\n",ans); }
|