题目链接:https://vjudge.net/problem/HDU-6599
双倍回文,即要回文串的一半也是一个回文串(对于“一半”的定义具体要看题目)
思路:因为回文树中的fail指针可以遍历到当前结点的所有后缀回文串,所以很容易想到,我们可以直接遍历每个回文串的fail,看它的后缀回文串中是否存在一个长度刚好是它的一半的。但是每次遍历fail要花费一定的时间,所以类似AC自动机的last跳板,我们可以用一个trans数组记录每个回文串的长度≤它的一半的后缀回文串,然后每次找长度为当前回文串一半的后缀回文串时,我们就可以直接从父亲的trans处开始遍历,而不是每次都从父亲的fail开始遍历。因为大于父亲长度一半的结点得到的结果肯定也会大于当前节点所表示的回文串长度的一半。实现过程就是代码if(!net[cur][c])中的if else部分
代码:
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <algorithm>
#include <iostream>
#include <queue>
using namespace std;
//#define int long long
const int manx=3e5+10;
const int max_len=2e6+10;
char s[manx];
int ans[manx];
struct pal_tree
{
int net[manx][26],cnt[manx],S[manx],len[manx],fail[manx],trans[manx];
int p,last,n;
int newnode(int l)
{
for(int i=0;i<26;i++)
net[p][i]=0;
cnt[p]=0;
len[p]=l;
return p++;
}
void init()
{
p=n=0;
newnode(0);
newnode(-1);
last=0;
fail[0]=1;
S[0]=-1;
}
int getfail(int x)
{
while(S[n-len[x]-1]!=S[n])x=fail[x];
return x;
}
void add(int c)
{
c-='a';
S[++n]=c;
int cur=getfail(last);
if(!net[cur][c])
{
int now=newnode(len[cur]+2);
fail[now]=net[getfail(fail[cur])][c];
net[cur][c]=now;
//num[now]=num[fail[now]]+1;
if(len[now]<=2)trans[now]=fail[now];
else
{
int temp=trans[cur];
while(S[n-len[temp]-1]!=S[n]||((len[temp]+2)>(len[now]+1)/2))temp=fail[temp];
trans[now]=net[temp][c];
}
}
last=net[cur][c];
cnt[last]++;
}
void ccount()
{
for(int i=p-1;i>=1;i--)
{
cnt[fail[i]]+=cnt[i];
if(len[i]<=2||(len[i]+1)/2==len[trans[i]])
ans[len[i]]+=cnt[i];
}
}
}tree;
int main()
{
while(scanf("%s",s)!=EOF)
{
memset(ans,0,sizeof(ans));
int l=strlen(s);
tree.init();
for(int i=0;i<l;i++)tree.add(s[i]);
tree.ccount();
for(int i=1;i<=l;i++)
printf("%d%c",ans[i],i==l?'\n':' ');
}
return 0;
}