题意:找到所有的回文串满足它的前一半也是回文串。
思路:回文自动机 + 树上倍增
我们要处理两件事情,第一件是每种回文串出现了多少次。我们回想回文自动机的构造过程,发现fail指针只会由标号大的指向标号小的。这样我们只需要标号从大到小遍历即可处理出每种回文串出现的次数。
第二件是倍增的写法,倍增是不需要初始化的因为我们只需要保证 mn[ x ][ 0 ]是对的,后面递推出来的一定是对的。还有根的mn[ root ][ 1 ] ...... mn[ root ][ 20 ]也得是对的。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 300005;
char str[maxn];
LL ans[maxn];
struct PAM{
int last,n,tot;
int ch[maxn][26],fail[maxn],len[maxn],vis[maxn],s[maxn],fa[maxn][21],mn[maxn][21];
LL cnt[maxn];
void init( int _n ){
tot = 1,len[1] = -1,s[0] = -1,fail[0] = 1;last = 1;n = 0;
for( int i = 0;i <= _n+1;i++ ){
memset( ch[i],0,sizeof(ch[i]) );
cnt[i] = 0;
}
}
int getfail(int x){
while( s[n-len[x]-1 ]!=s[n] )x=fail[x];
return x;
}
void add(int t){
s[++n] = t;int cur = getfail(last);
if(!ch[cur][t]){
fail[++tot]=ch[getfail(fail[cur])][t];
ch[cur][t]=tot,len[tot]=len[cur]+2;
}
last=ch[cur][t];
cnt[last]++;
}
void count(){
for( int i = 2;i <= tot;i++ ){
fa[i][0] = fail[i];
for( int j= 1;j <= 20;j++ ){
fa[i][j] = fa[ fa[i][j-1] ][j-1];
}
}
for( int i = 2;i <= tot;i++ ){
mn[i][0] = len[ fa[i][0] ];
for( int j = 1;j <= 20;j++ ){
mn[i][j] = min( mn[i][j-1],mn[fa[i][j-1]][j-1] );
}
}
for( int i = tot;i >= 2;i-- ){
cnt[ fail[i] ] += cnt[i];
}
int res = 0;
for( int i = 2;i <= tot;i++ ){
int p = i;
for( int t = 20;t >= 0;t-- ){
if( mn[p][t] >= (len[i]+1)/2 ){
p = fa[p][t];
}
}
if( len[p] == (len[i]+1)/2 ){
ans[len[i]] += cnt[i];
}
}
}
}g;
int main(){
while( EOF != scanf("%s",str) ){
int len = strlen(str);
g.init(len);
memset( ans,0,sizeof(long long)*(len+1) );
for( int i = len-1;i >= 0;i-- ){
g.add(str[i]-'a' );
}
g.count();
printf("%lld",ans[1]);
for( int i = 2;i <= len;i++ ){
printf(" %lld",ans[i]);
}
puts("");
}
return 0;
}