luogu P4199 万径人踪灭
LINK:万径人踪灭
很妙的一道题 说明我NTT的意识还不够.
题目中的一个非常特殊的条件 字符串只有a,b两种。
考虑求出所有的方案 连续的一段回文串不算 间隔的才算 发现我们如果去枚举间隔 再统计回文串会非常麻烦。
显然我们求出这个字符串中所有的 不管有没有间隔的回文串都求出来 最后减掉一些没有间隔的回文串即可。
考虑后半部分 没有间隔 每个回文串 我们以回文中心为标记统计方案 显然manacher一下即可。
考虑前半部分 还是以回文中心来判断每一个方案 这样可以做到不重不漏。
对于某个回文中心i来说 如果存在 \(S_{i+j}==S_{i-j}\)那么所有这个j点可以被使用。要统计出来有多少个j.
暴力统计是\(n^2\)的。但是manacher也不行。
但是这类似于字符串的特殊匹配问题 一般考虑转换成多项式卷积问题。
判断卷积问题 一般要发现下标的特殊性.\(i+j+i-j=2\cdot i\) 至此 我们发现来一发卷积 如果能够匹配的话贡献为1 且对于i这个位置 所有的j的贡献 被统计在了2i的位置上。
考虑题目中的特殊条件 只有a,b两种 这样算贡献的时候先将a的位置赋值为1 再做b.即可。
值得注意的是 最后需要判断一下是否i为偶数位 因为其回文中心也被算进去了. 需要特判一下.这里可以使用NTT 因为答案绝对不会超过mod.
const int MAXN=300010,G=3;
int n,lim,cnt;ll ans;
int a[MAXN],b[MAXN],p[MAXN],rev[MAXN];
char ch[MAXN],c[MAXN];
inline int ksm(ll b,int p,int M)
{
ll cnt=1;
while(p)
{
if(p&1)cnt=cnt*b%M;
b=b*b%M;p=p>>1;
}
return cnt;
}
inline void NTT(int *a,int op)
{
rep(0,lim-1,i)if(i<rev[i])swap(a[i],a[rev[i]]);
for(int len=2;len<=lim;len=len<<1)
{
int mid=len>>1;
ll wn=ksm(G,op==1?(mod-1)/len:mod-1-(mod-1)/len,mod);
for(int j=0;j<lim;j+=len)
{
ll d=1;
for(int i=0;i<mid;++i)
{
int x=a[i+j];int y=(ll)a[i+j+mid]*d%mod;
a[i+j]=(x+y)%mod;a[i+j+mid]=(x-y+mod)%mod;
d=d*wn%mod;
}
}
}
if(op==-1)
{
ll inv=ksm(lim,mod-2,mod);
rep(0,lim-1,i)a[i]=a[i]*inv%mod;
}
}
inline void manacher()
{
int mx=0,mid=0;
rep(1,cnt,i)
{
if(i<mx)p[i]=min(p[(mid<<1)-i],mx-i);
else p[i]=1;
while(c[i-p[i]]==c[i+p[i]])++p[i];
if(p[i]+i>mx)mx=p[i]+i,mid=i;
ans=(ans-(p[i]-1+((p[i]-1)&1))/2)%P;
}
}
int main()
{
freopen("1.in","r",stdin);
scanf("%s",ch);n=strlen(ch);
rep(0,n-1,i)if(ch[i]=='a')a[i]=1;else b[i]=1;
lim=1;while(lim<n+n)lim=lim<<1;
rep(0,lim-1,i)rev[i]=rev[i>>1]>>1|((i&1)?lim>>1:0);
NTT(a,1);NTT(b,1);
rep(0,lim-1,i)a[i]=(ll)a[i]*a[i]%mod,b[i]=(ll)b[i]*b[i]%mod;
NTT(a,-1);NTT(b,-1);
rep(0,lim-1,i)a[i]+=b[i];
rep(0,lim-1,i)
{
if(!a[i])continue;
if(!(i&1))
{
int ww=(a[i]+1)>>1;
ans=(ans+ksm(2,ww,P)-1)%P;
}
else
{
int ww=a[i]>>1;
ans=(ans+ksm(2,ww,P)-1)%P;
}
}
c[0]='&';c[cnt=1]='#';
rep(0,n-1,i)c[++cnt]=ch[i],c[++cnt]='#';
manacher();putl((ans+P)%P);return 0;
}