【洛谷P4173】残缺的字符串
题目
题目链接:https://www.luogu.com.cn/problem/P4173
很久很久以前,在你刚刚学习字符串匹配的时候,有两个仅包含小写字母的字符串\(A\)和\(B\),其中\(A\)串长度为\(m\),\(B\)串长度为\(n\)。可当你现在再次碰到这两个串时,这两个串已经老化了,每个串都有不同程度的残缺。
你想对这两个串重新进行匹配,其中\(A\)为模板串,那么现在问题来了,请回答,对于\(B\)的每一个位置\(i\),从这个位置开始连续\(m\)个字符形成的子串是否可能与\(A\)串完全匹配?
\(m\leq n\leq 3\times 10^5\)。
思路
给每一个字母一个权值,那么一个区间 \([l,r]\) 如果可以匹配,那么必然满足
\[\sum^{r}_{i=l}a_ib_i(a_i-b_i)^2=0
\]
设 \(f_i\) 表示位置 \(i\) 的权值(也就是如果上式 \(l=i,r=i+n-1\)),那么有
\[f_i=\sum^{n}_{j=1}a_jb_{i+j-1}(a_j-b_{i+j-1})^2
\]
不难想到把 \(a\) 逆序,拆开括号,并且把 \(a_{n+1\sim m}\) 设为 \(0\),
\[f_{m-i}=\sum^{m}_{j=1}a_{m-j}^3b_{i+j}+\sum^{m}_{j=1}a_{m-j}b^3_{i+j}-\sum^{m}_{j=1}2a_{m-j}^2b_{i+j}^2
\]
跑三次 FFT 即可。常数较大,开 O2 才过。
时间复杂度 \(O(n\log n)\)。
代码
#include <bits/stdc++.h>
#define cp complex<double>
using namespace std;
const int N=1050000;
const double eps=0.5,pi=acos(-1);
int n,m,lim,a[N],b[N],rev[N];
cp f[N],g[N],h[3][N];
char s[N],t[N];
queue<int> qans;
int c[N];
void FFT(cp *f,int tag)
{
for (int i=0;i<lim;i++)
if (i<rev[i]) swap(f[i],f[rev[i]]);
for (int k=1;k<lim;k<<=1)
{
cp tmp(cos(pi/k),tag*sin(pi/k));
for (int i=0;i<lim;i+=(k<<1))
{
cp w(1,0);
for (int j=0;j<k;j++,w=w*tmp)
{
cp x=f[i+j],y=w*f[i+j+k];
f[i+j]=x+y; f[i+j+k]=x-y;
}
}
}
}
int main()
{
scanf("%d%d%s%s",&n,&m,s+1,t+1);
for (int i=1;i<=n;i++) a[i]=(s[i]-'a'+1)*(s[i]!='*');
for (int i=1;i<=m;i++) b[i]=(t[i]-'a'+1)*(t[i]!='*');
reverse(a+1,a+1+m);
lim=1;
while (lim<=2*m) lim<<=1;
for (int i=0;i<lim;i++)
rev[i]=(rev[i>>1]>>1)|((i&1)?(lim>>1):0);
for (int i=0;i<lim;i++)
{
f[i]=cp(1.0*a[i]*a[i]*a[i],0);
g[i]=cp(1.0*b[i],0);
}
FFT(f,1); FFT(g,1);
for (int i=0;i<lim;i++) h[0][i]=f[i]*g[i];
FFT(h[0],-1);
for (int i=0;i<lim;i++)
{
f[i]=cp(1.0*a[i],0);
g[i]=cp(1.0*b[i]*b[i]*b[i],0);
}
FFT(f,1); FFT(g,1);
for (int i=0;i<lim;i++) h[1][i]=f[i]*g[i];
FFT(h[1],-1);
for (int i=0;i<lim;i++)
{
f[i]=cp(2.0*a[i]*a[i],0);
g[i]=cp(1.0*b[i]*b[i],0);
}
FFT(f,1); FFT(g,1);
for (int i=0;i<lim;i++) h[2][i]=f[i]*g[i];
FFT(h[2],-1);
for (int i=1;i<=m-n+1;i++)
if (fabs((h[0][m+i].real()+h[1][m+i].real()-h[2][m+i].real())/lim)<eps)
qans.push(i);
printf("%d\n",qans.size());
for (;qans.size();qans.pop())
printf("%d ",qans.front());
return 0;
}