题意:
给你一个长度为\(n\)的字符串,现在问你长度为\(1,2\dots n\)的子串中,有多少个子串\(str\)是回文串,且它的长度缩短到$\lceil \frac{|str|}{2} \rceil $后也是回文串。
分析:
很明显我们可以分别利用回文树中的\(cnt\)数组和\(fail\)数组,把任意长度为\(len\)的字符串的回文子串和它的最长回文后缀在\(\mathcal{O}(n)\)的时间处理出来。知道如何去求出上述的东西之后,我们只需要考虑如何去剔除一些不合法的回文子串。
对于一个回文串\(str\),想让他的前一半回文相当于让他后一半回文,那么如果他的最长回文后缀长度小于\(\frac{|str|}{2}\),显然是不行的,所以考虑最长回文后缀长度大于等于\(\frac{|str|}{2}\)的情况。我们发现如果想让它的后一半回文,那么就是要缩短删掉若干个原串和最长回文后缀的差,因此我们只需要判断$\lfloor \frac{|str|}{2} \rfloor$ 是否能够整除 \((|str|-|str_{maxsuffix}|)\)即可
代码:
#include <bits/stdc++.h>
#define maxn 300005
using namespace std;
char str[maxn];
typedef long long ll;
struct PAM{//回文树
int next[maxn][26],fail[maxn],len[maxn],cnt[maxn],S[maxn];
int id,n,last;
int newnode(int x){
for(int i=0;i<26;i++){
next[id][i]=0;
}
cnt[id]=0;
len[id]=x;
return id++;
}
void init(){
id=0;
newnode(0);
newnode(-1);
fail[0]=1;
S[0]=-1;
last=n=0;
}
int getfail(int x){
while(S[n-len[x]-1]!=S[n]) x=fail[x];
return x;
}
void Insert(int c){
c-='a';
S[++n]=c;
int cur=getfail(last);
if(!next[cur][c]){
int now=newnode(len[cur]+2);
fail[now]=next[getfail(fail[cur])][c];
next[cur][c]=now;
}
last=next[cur][c];
cnt[last]++;
}
void getsum(){//自下向上更新
for(int i=id-1;i>=0;i--){
cnt[fail[i]]+=cnt[i];
}
}
}pam;
int Ans[maxn];
bool judge(int x,int Len){
int tmp1=pam.len[pam.fail[x]];
int tmp2=pam.len[x]-tmp1;
if((pam.len[x]/2)%tmp2==0) return true;
else return false;
}
int main()
{
while(~scanf("%s",str)){
pam.init();
int len=strlen(str);
for(int i=0;i<=len;i++){
Ans[i]=0;
}
for(int i=0;i<len;i++){
pam.Insert(str[i]);
}
pam.getsum();
ll res=0;
for(int i=2;i<pam.id;i++){
if(judge(i,(pam.len[i]+1)/2)){
Ans[pam.len[i]]+=pam.cnt[i];
}
}
for(int i=1;i<=len;i++){
if(i==1) printf("%d",Ans[i]);
else printf(" %d",Ans[i]);
}
puts("");
}
return 0;
}