构建出回文树,那么我们需要对于回文树的每一个节点求出它在原串中的出现次数和前\(\frac{len}{2}\)个字符构成的串
前者可以直接在fail树上传递endpos标记,后者可以倍增。但是后者还有一种复杂度\(O(?)\)的做法跑得非常快:
对于每一个点维护\(half_x\)表示点\(x\)对应的串中\(\leq \frac{len_x}{2}\)的最长的回文后缀在PAM的位置。新插入一个节点时,从它的trans的父亲的half开始,暴力跳fail直到找到当前节点的half。对于长度为\(1\)的节点单独判断一下。
所以上面的做法到底是不是\(O(n)\)的呢……
回文树一会儿不写全部忘光
#include<bits/stdc++.h>
//this code is written by Itst
using namespace std;
const int _ = 1e5 + 7;
char s[_];
namespace PAM{
int trans[_][26] , len[_] , fa[_] , hlf[_] , val[_] , endpos[_];
int cnt , lst;
#define clr(x) memset(x , 0 , sizeof(x));
void init(){
cnt = 1; lst = 0;
clr(trans); clr(len); clr(fa); clr(hlf);
clr(val); clr(endpos);
fa[1] = fa[0] = 1; len[1] = -1;
}
void extend(int pos){
int p = lst;
while(s[pos] != s[pos - len[p] - 1]) p = fa[p];
if(trans[p][s[pos] - 'a']){
lst = trans[p][s[pos] - 'a']; ++endpos[lst];
return;
}
int k = ++cnt , q = fa[p];
while(s[pos] != s[pos - len[q] - 1]) q = fa[q];
fa[k] = trans[q][s[pos] - 'a']; trans[p][s[pos] - 'a'] = k;
len[k] = len[p] + 2; lst = k; ++endpos[lst];
if(len[k] <= 1) val[hlf[k] = k] = 1;
else{
p = hlf[p];
while(s[pos - len[p] - 1] != s[pos] || len[p] + 2 > len[k] / 2)
p = fa[p];
hlf[k] = trans[p][s[pos] - 'a'];
val[k] = 1 + (len[hlf[k]] == len[k] / 2 ? val[hlf[k]] : 0);
}
}
long long calc(){
for(int i = cnt ; i >= 1 ; --i)
endpos[fa[i]] += endpos[i];
long long ans = 0;
for(int i = 2 ; i <= cnt ; ++i)
ans += 1ll * val[i] * endpos[i];
return ans;
}
}
int main(){
#ifndef ONLINE_JUDGE
freopen("in","r",stdin);
//freopen("out","w",stdout);
#endif
int T;
for(scanf("%d" , &T) ; T ; --T){
scanf("%s" , s + 1); int L = strlen(s + 1);
PAM::init();
for(int i = 1 ; i <= L ; ++i)
PAM::extend(i);
cout << PAM::calc() << endl;
}
return 0;
}