(不知道xjb KMP可不可以做的说)
(假设下标都以0开头)
对于有一定偏移量的序列的 对应位置 匹配或者数值计算的题,这里是有一种套路的,就是把其中一个序列翻转过来,然后卷积一下,所得到的新序列C的每一个位置就包含了 所有原来一定偏移量的位置的乘积和。
对于这个题,我们只需要找到一种方法,使相同的字符代表的数乘积是一个特殊的值,然后*可以看成0(*可以匹配任意字符),这样使得卷积后的位置是特殊值的就可以匹配。
而且这种特殊值需要满足: 两个特殊值相加之后还是特殊值,两个不是特殊值相加还不是特殊值,一个是一个不是相加也不是特殊值。
如果要去找到所有满足这个性质的集合的话,貌似是比较困难的QWQ,哪怕就要找一个我也不会啊QWQ
但是有一种很好的方法可以拟合这个集合,那就是设特殊值是整数,然后第一个串里某个字符的权值和第二个串中的这种字符的权值互为倒数。
当第一个串中的字符权值是完全在实数域上随机的话,那么就几乎可以拟合上述集合了,因为可以证明若干个随机实数的和为整数的概率趋近于0.
介于我一直对luogu的<ctime>有心理阴影,所以这里的权值并不是随机的,而是每个字符的编码+2333.
(结果竟然过了2333)
/* f[i] -> 开头的偏移量 = i-m+1 的val_sum 所以统计 f[m-1] ~ f[n-1] 即可 (对应偏移量 0 ~ n-m) */ #include<bits/stdc++.h> #define ll long long #define D long double #define E complex<long double> using namespace std; const D pi=acos(-1),eps=1e-9; const int maxn=300005; E a[maxn*4],b[maxn*4]; int r[maxn*4],n,m,N,ans,p[maxn],l; char S[maxn],s[maxn]; inline bool isZ(D x){ return fabs(x-floor(x+0.5))<=eps;} inline bool isL(char x){ return x>='a'&&x<='z';} inline void build(){ for(int i=0;i<m;i++) if(isL(s[i])) a[m-i-1]=1/(D)(s[i]+2333); for(int i=0;i<n;i++) if(isL(S[i])) b[i]=S[i]+2333; for(N=1;N<(n+m-1);N<<=1) l++; for(int i=0;i<N;i++) r[i]=(r[i>>1]>>1)|((i&1)<<(l-1)); } inline void FFT(E *c,int f){ for(int i=0;i<N;i++) if(i<r[i]) swap(c[i],c[r[i]]); for(int i=1;i<N;i<<=1){ E omega(cos(pi/i),f*sin(pi/i)); for(int P=i<<1,j=0;j<N;j+=P){ E now(1,0); for(int k=0;k<i;k++,now*=omega){ E x=c[k+j],y=c[j+k+i]*now; c[j+k]=x+y; c[j+k+i]=x-y; } } } if(f==-1) for(int i=0;i<N;i++) c[i]/=N; } inline void solve(){ FFT(a,1),FFT(b,1); for(int i=0;i<N;i++) a[i]*=b[i]; FFT(a,-1); for(int i=m-1;i<n;i++) if(isZ(a[i].real())) p[++ans]=i-m+2; } int main(){ scanf("%d%d",&m,&n); scanf("%s",s),scanf("%s",S); build(),solve(); printf("%d\n",ans); for(int i=1;i<=ans;i++) printf("%d ",p[i]); return 0; }