[luogu][1]
题意
求字符串中相交(含公共部分,所以包含也算相交)的回文子串的对数。
模一个并不是质数的数。
sol
正难则反吧。
总回文子串的个数就是回文树上的\(\sum dep_i\)
所以总对数就是\(\frac 12\sum dep_i*(\sum dep_i-1)\)
然后就是要求不相交的回文串对数。
枚举前面那个回文子串的终止位置\(i\),那么后一个回文子串的起始位置至少在\(i+1\)之后。
所以正反建两遍回文树,求出以每一个\(i\)为起始位置/终止位置的回文串个数,然后乘一下就行了。
然而。。。
卡空间。裸的回文树$O(26n)$的空间会被卡掉。
所以可以不开数组转而用邻接表存转移状态,相当于是把$26$这个常数从空间转到了时间上面去。
##code
```cpp
#include
#include
#include
using namespace std;
const int N = 2e6+5;
const int mod = 51123987;
int n,fa[N],len[N],dep[N],tot,last,p1[N],p2[N],ans;
int to[N],nxt[N],ww[N],head[N],cnt;
char s[N];
void init()
{
fa[last=0]=fa[1]=1;
len[tot=1]=-1;
memset(head,0,sizeof(head));cnt=0;
}
void link(int u,int v,int c)
{
to[++cnt]=v;nxt[cnt]=head[u];ww[cnt]=c;
head[u]=cnt;
}
int tr(int v,int c)
{
for (int e=head[v];e;e=nxt[e])
if (ww[e]==c) return to[e];
return 0;
}
void extend(int c,int n)
{
int v=last;
while (s[n-len[v]-1]!=s[n]) v=fa[v];
if (!tr(v,c))
{
int u=++tot,k=fa[v];
len[u]=len[v]+2;
while (s[n-len[k]-1]!=s[n]) k=fa[k];
fa[u]=tr(k,c);dep[u]=dep[fa[u]]+1;
link(v,u,c);
}
last=tr(v,c);
}
int main()
{
scanf("%d",&n);
scanf("%s",s+1);
init();
for (int i=1;i<=n;++i) extend(s[i]-'a',i),(ans+=(p1[i]=dep[last]))%=mod;
ans=1ll*ans*(ans-1)/2%mod;
reverse(s+1,s+n+1);
init();
for (int i=1;i<=n;++i) extend(s[i]-'a',i),p2[n-i+1]=dep[last];
for (int i=n;i;--i) (p2[i]+=p2[i+1])%=mod;
for (int i=1;i<=n;++i) ans=(ans-1ll*p1[i]*p2[i+1]%mod+mod)%mod;
printf("%d\n",ans);
return 0;
}
```
[1]: https://www.luogu.org/problemnew/show/CF17E