题意
给定一个只含小写字母的字符串S.如果S[i……j]与S[(j+1)……k]均为回文串,则(i,j,k)是一个三元组。
对于所有的三元组,求:
∑i⋅k
数据范围
对于30%的数据,1≤|S|≤100
对于60%的数据,1≤|S|≤10000
对于100%的数据,1≤|S|≤1000000
多组数据,数据不超过5组.
题解
manacher+差分.
跑一遍manacher,找到每个位置的最远扩展距离,然后区间修改。
我们发现区间修改的值是一个等差数列,即便是用线段树维护,也逃不脱差分。
而且数据范围明显要求我们用O(n)的做法,所以线段树行不通。
于是我们可以用差分,一个数组记录等差首项,另一个记录公差的相关信息。
然而我考场上写的是
O(n2)
的暴力,居然有70分。
后来看了一下题解,就凭着感觉自己写了一份manacher+差分数组的代码,竟然A了。
具体细节,草稿纸上算几个例子就明白了。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define MAXN 1005000
#define mod 1000000007
char s[MAXN],t[MAXN<<1];
int n,T,p[MAXN<<1];
long long a[MAXN],b[MAXN],c[MAXN],d[MAXN];
void ch1(int x,int l,int l2)
{
//x,x-l+1
//x-1,x-l
if(l2&1)
{
a[x]+=x;
a[x+l]-=x-l+1;
b[x+1]-=1;
b[x+l]+=1;
}
else
{
a[x]+=x-1;
a[x+l]-=x-l;
b[x+1]-=1;
b[x+l]+=1;
}
return ;
}
void ch2(int x,int l,int l2)
{
if(l2&1)
{
c[x-l+1]+=x+l-1;
c[x+1]-=x;
d[x-l+2]-=1;
d[x+1]+=1;
}
else
{
c[x-l+1]+=x+l;
c[x+1]-=x+1;
d[x-l+2]-=1;
d[x+1]+=1;
}
return ;
}
void solve()
{
for(int i=1;i<=n;i++)
{
b[i]+=b[i-1];
d[i]+=d[i-1];
}
for(int i=1;i<=n;i++)
{
a[i]=a[i-1]+a[i];
a[i]=a[i]+b[i];
c[i]=c[i-1]+c[i];
c[i]=c[i]+d[i];
}
return ;
}
void work()
{
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
memset(c,0,sizeof(c));
memset(d,0,sizeof(d));
scanf("%s",s+1);
n=strlen(s+1);
int pos=0;
t[pos]='$';
for(int i=1;i<=n;i++)
{
t[++pos]='#';
t[++pos]=s[i];
}
t[++pos]='#';
int id=0,mx=0;
for(int i=1;i<pos;i++)
{
p[i]=mx>i?min(mx-i,p[2*id-i]):1;
while(t[i+p[i]]==t[i-p[i]])
{
++p[i];
}
if(p[i]>1)
{
ch1((i+1)>>1,p[i]>>1,p[i]-1);
ch2(i>>1,p[i]>>1,p[i]-1);
}
if(p[i]+i>mx)
{
mx=p[i]+i;
id=i;
}
}
solve();
int ans=0;
for(int i=1;i<n;i++)
{
ans+=1ll*a[i]%mod*c[i+1]%mod;
ans-=ans>=mod?mod:0;
//printf("i%d a%d c%d\n",i,a[i],c[i]);
}
printf("%d\n",ans);
return ;
}
int main()
{
scanf("%d",&T);
for(int i=1;i<=T;i++)
{
work();
}
return 0;
}