UVALive - 4671 K-neighbor substrings (FFT+哈希+set)

10 篇文章 0 订阅
8 篇文章 0 订阅

题目传送门

点这里

题目大意

有两个只包含’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;
}

这里写图片描述

无声告白。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值