回文树:可以用个树表示一个串里面所有为回文串的字符。
贴一个大佬的理解:
https://blog.csdn.net/qq_36551189/article/details/79245675
我感觉跟后缀自动机有点像,分成两个树,长度为奇数的回文,长度为偶数的回文,树上的节点就代表从跟出发,炎沿树上的边走,得到的就是回文的一半。
构造:每个节点有个fail指针,指向当先串的最长后缀为一个回文串,那么可以通过get_fail函数得到cur这个以当前位置为结尾能得到的最长回文,
构造当前节点的fail:对fail[cur]求一个get_fail就可以了,当前长度是len[cur]+2。
这里有个很巧妙的地方,0作为偶数串的根,1作为奇数串的根,len[1] 设为-1。fail[0] = 1,这样如果一个串找不到cur的话,就可以把自己作为一个奇数回文串了。
复杂度的证明:设当前节点的深度为x,那么getfail得到的cur节点的深度一定小于last,x节点深度为cur深度+1,每次操作的时间主要在getfail中,每次getfail最少让节点的深度减1,那么没操作的次数最多为last的深度减去cur的深度再加1,因为x节点再cur下面,又因为last会被赋值为x所在的节点,所以操作次数就是last前后位置深度差+2,所以可以证明操作次数最多为2len(len为字符串长度),如果这里不理解可以去看一下势能摊还分析法。
这事第一个getfailed的复杂度。
第二个getfail的复杂度证明:把getfail想象成一个树,可以吧i,当做getfail(i)的一个儿子,那么跟第一个getfail的情况就一样了,所以可以证明复杂度在2len之内。
貌似回文树最大时间就在于newnode的初始化了,,,
#include<bits/stdc++.h>
using namespace std;
const int N = 2e6+100;
const int mod = 1e9+7;
int qp[N];
struct Palindromic_Tree{
int s[N],nex[N][10],fail[N],last,n,cnt[N],len[N],tp;
int ret[N];
int newnode(int x){
for(int i = 0;i< 10;i ++) nex[tp][i] =0;
cnt[tp] = 0;
len[tp] = x;
return tp++;
}
void init(){
tp = n = last = 0;
newnode(0);
newnode(-1);
fail[0] = 1;
s[n] = -1;
}
int get_fail(int x){
while(s[n-len[x]-1] != s[n]) x= fail[x];
return x;
}
void add(int x){
s[++n] = x;
int cur = get_fail(last);
if(!nex[cur][x]){
int np = newnode(len[cur]+2);
fail[np] = nex[get_fail(fail[cur])][x];
nex[cur][x] = np;
}
last = nex[cur][x];
cnt[last]++;
}
int query(){
ret[0] =0,ret[1] = 0;
int ans = 0;
for(int i = 0;i < tp;i ++){
//cout << i <<' '<< ret[i] << endl;
ans += ret[i];
ans %= mod;
for(int j = 0;j < 10;j ++){
if(nex[i][j] == 0) continue;
//cout <<i << ' ' <<j << ' '<<nex[i][j] << endl;
if(i == 1){
ret[nex[i][j]] = j;
}
else{
ret[nex[i][j]] = (1LL*10*ret[i]+1ll*(qp[len[nex[i][j]]-1]+1)*j)%mod;
}
}
}
return ans;
}
}pat;
char st[N];
int main(){
qp[0] = 1;
for(int i = 1;i < N;i ++) qp[i] = 1LL*qp[i-1]*10%mod;
scanf("%s",st);
pat.init();
int len = strlen(st);
for(int i = 0;i < len;i ++){
pat.add(st[i]-'0');
}
printf("%d\n",pat.query());
return 0;
}