题目传送门
题目大意
有两个只包含’a’和’b’的字符串A和B,求A中与B的Hamming距离不超过K的本质不同的子串的数量。
字符串长度范围为[1,100000],K的范围为[0,100000]。
解题思路
这是一道巧妙的利用FFT的字符串题。
首先将两个字符串用多项式表示, 因为字符只有’a’和’b’, 我们就用0和1来表示原字符位置上对应的项的系数。
首先如果用1表示’a’, 0表示’b’的话对于A的子串和B怎么判断其有多少位不同呢? 其实可以反过来考虑, 考虑有多少位是相同的。
我们先把’a’记作1,假如我们将串B反过来写, 然后和A做多项式乘法, 那么只有当两个位置都为’a’乘积所对应的那一项结果的系数会+1。这里需要解释一下,所谓对应的那一项所累积加的就是位置和相同的项的乘积,这就是要将B串反过来的原因。举个栗子:多项式C在3位置上的系数就等于A在0,B在3,A在1,B在2…位置上的乘积的总和,因为位置和幂是一样的,位置的和相同那么幂的和相同,对应原串就是两个相同位置的字符的乘积。
接着我们再把’b’记做1, ‘a’记做0, 再做一次FFT就可以得到多少位置都是’b’, 这样就能得到对应一段A的子串和B的相同位置个数了。然后我们就巧妙地求出了以每一位结尾的与B长度相同的子串与B的Hamming距离了。
对于去掉相同的子串, 将字符串hash然后丢进set里去重即可(不用set也还有很多其他作法)。不过博主我在这里被卡了好久,hash换了各种模还是WA,双hashWA,找网上别人程序对拍十几组错一组,我改hash改到疯,最后仿照标程开unsigned long long,让hash值自然溢出,终于AC了!(我不理解为什么会这样,QAQ)
做完这题总结出一个套路, 将字符串转换成整数,将B反向后,构造A和B的多项式做卷积能将相同位置的贡献聚集到结果的同一次幂的项上。
代码
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <map>
#include <set>
#define MAXN 100010
#define Mod1 1000000007ll
#define Mod2 2333333333ll
using namespace std;
typedef unsigned long long LL;
set <LL> S;
const double PI = acos(-1.0);
int n, K, Case;
char s1[MAXN], s2[MAXN];
int P[MAXN], ans;
LL Hash[MAXN], f[MAXN], seed = 500009uL;
struct Complex{
double real, image;
Complex() {}
Complex(double _real, double _image){
real = _real;
image = _image;
}
friend Complex operator + (Complex A, Complex B){return Complex(A.real + B.real, A.image + B.image);}
friend Complex operator - (Complex A, Complex B){return Complex(A.real - B.real, A.image - B.image);}
friend Complex operator * (Complex A, Complex B){return Complex(A.real * B.real - A.image * B.image, A.real * B.image + A.image * B.real);}
}a[MAXN<<2], b[MAXN<<2], c1[MAXN<<2], c2[MAXN<<2];
void Get_P(){
for(int i = 1, t = 1; i < MAXN; i++){
if(t < i) t <<= 1;
P[i] = t;
}
}
void Reverse(Complex *A){
for(int i = 1; i < n-1; i++){
int j = 0;
for(int k = 1, tmp = i; k < n; k <<= 1, tmp >>= 1)
j = ((j << 1) | (tmp & 1));
if(j > i) swap(A[i], A[j]);
}
}
void FFT(Complex *A, int n, int DFT){
Reverse(A);
for(int s = 1; (1<<s) <= n; s++){
int m = (1 << s);
Complex wm = Complex(cos(DFT*2*PI/m), sin(DFT*2*PI/m));
for(int k = 0; k < n; k += m){
Complex w = Complex(1, 0);
for(int j = 0; j < (m>>1); j++){
Complex u = A[j + k], t = w * A[j + k + (m>>1)];
A[j + k] = u + t;
A[j + k + (m>>1)] = u - t;
w = w * wm;
}
}
}
if(DFT == -1) for(int i = 0; i < n; i++) A[i].real /= n, A[i].image /= n;
}
int main(){
freopen("LA4671.in", "r", stdin);
freopen("LA4671.out", "w", stdout);
Get_P();
while(~ scanf("%d", &K) && (~ K)){
++ Case;
scanf("%s%s", &s1, &s2);
int len1 = strlen(s1), len2 = strlen(s2);
if(len1 < len2){
printf("Case %d: %d\n", Case, 0);
continue;
}
n = P[max(len1, len2)] << 1;
for(int i = 0; i < len1; i++) a[i] = Complex(s1[i]=='a', 0);
for(int i = len1; i < n; i++) a[i] = Complex(0, 0);
for(int i = 0; i < len2; i++) b[len2-i-1] = Complex(s2[i]=='a', 0);
for(int i = len2; i < n; i++) b[i] = Complex(0, 0);
FFT(a, n, 1);
FFT(b, n, 1);
for(int i = 0; i < n; i++) c1[i] = a[i] * b[i];
FFT(c1, n, -1);
for(int i = 0; i < len1; i++) a[i] = Complex(s1[i]=='b', 0);
for(int i = len1; i < n; i++) a[i] = Complex(0, 0);
for(int i = 0; i < len2; i++) b[len2-i-1] = Complex(s2[i]=='b', 0);
for(int i = len2; i < n; i++) b[i] = Complex(0, 0);
FFT(a, n, 1);
FFT(b, n, 1);
for(int i = 0; i < n; i++) c2[i] = a[i] * b[i];
FFT(c2, n, -1);
S.clear();
f[0] = 1ull;
for(int i = 1; i <= len1; i++) f[i] = f[i-1] * seed;
Hash[0] = 0ull;
for(int i = 1; i <= len1; i++)
Hash[i] = Hash[i-1] * seed + s1[i-1] - 'a' + 1;
ans = 0;
for(int i = len2-1; i < len1; i++)
if(len2 - (int)(c1[i].real+0.5) - (int)(c2[i].real+0.5) <= K){
LL key = Hash[i+1] - Hash[i-len2+1] * f[len2];
S.insert(key);
}
printf("Case %d: %d\n", Case, S.size());
}
return 0;
}
无声告白。