HDU 6975 Forgiving Matching 快速傅里叶变换处理带通配符字符串匹配

文章目录


众所周知多校签到题中必有一道板子题,那么只要会使用板子就可以多做出一道签到了.
本题就是一道FFT的板子题.

题意

给出长度为 n n n的字符串 s s s,长度为 m m m的字符串 t t t,定义两字符串匹配是两个字符串对应不相等的位置数量不超过 k k k,其中通配符 ∗ * 能匹配任何一个字符.对 k ∈ [ 0 , m ] k\in[0,m] k[0,m]的每一个值输出 t t t s s s中能匹配的位置的数量.

题解

我们需要将 t t t在每一个位置不能匹配上的个数加入 c n t cnt cnt数组然后对其求一遍前缀和便能够得到答案.
c h ∈ [ " 0123456789 ∗ " ] ch\in["0123456789*"] ch["0123456789"] 11 11 11个字符分别做一遍 f f t fft fft,具体是令 a i a_i ai表示 s i s_i si是否等于 c h ch ch,令 b i b_i bi表示 t m − i + 1 t_{m-i+1} tmi+1是否等于 c h ch ch,然后 a × b a\times b a×b进行卷积即可,注意通配符可以匹配任何一个字符,所以特地把通配符拿出来做一次卷积,然后把乘出来为 1 1 1的部分减掉(两个都是通配符就重复计算了一次).最后单独加上两个字符串中通配符的贡献即可.
代码如下.

#include<bits/stdc++.h> //Ithea Myse Valgulious
typedef long long ll;
using namespace std;
const int yuzu=1<<20;
using db=double;
const db pi=3.14159265358979;
typedef int fuko[yuzu];
typedef char fuka[yuzu]; 
struct cpx {
  db x,y;
  cpx(db x,db y):x(x),y(y) {}
  cpx(db x=0):x(x),y(0) {}
  cpx operator +(const cpx &b) {
    return cpx(x+b.x,y+b.y);
  }
  cpx operator -(const cpx &b) {
    return cpx(x-b.x,y-b.y);
  }
  cpx operator *(const cpx &b) {
    return cpx(x*b.x-y*b.y,x*b.y+y*b.x);
  }
  cpx operator *(int o) {
    return cpx(x*o,y*o);
  }
}a[yuzu],b[yuzu],ans[yuzu];
fuko rv,cnts,zw;
void fft(cpx *a,int n,int rev) {
  for (int i=0;i<n;++i)
    if (i<rv[i]) swap(a[i],a[rv[i]]);
  for (int len=2;len<=n;len<<=1) {
    cpx now(cos(2*pi/len),sin(2*pi*rev/len));
    for (int st=0;st<n;st+=len) {
      cpx fl(1,0);
      for (int pos=st;pos<st+(len>>1);++pos) {
        cpx tmp=fl*a[pos+(len>>1)];
        a[pos+(len>>1)]=a[pos]-tmp;
        a[pos]=a[pos]+tmp;
        fl=fl*now;
      }
    }
  }
}
fuka s,t;
int main() {
  int i,ca,n,m;
  for (scanf("%d",&ca);ca--;) {
    scanf("%d%d%s%s",&n,&m,s,t);
    memset(zw,0,sizeof zw);
    memset(cnts,0,sizeof cnts);
    for (i=0;i<n;++i) {
      i?cnts[i]+=cnts[i-1]:0;
      cnts[i]+=s[i]=='*';
    }
    int cntt=0,len=1; for (;len<n+m;len<<=1);
    for (i=0;i<m;++i) cntt+=t[i]=='*';
    reverse(t,t+m);
    for (i=0;i<len;++i) ans[i]=0,rv[i]=(rv[i>>1]>>1)|(i&1?len>>1:0);
    for (char ch:"0123456789*") {
      for (i=0;i<n;++i) a[i]=s[i]==ch;
      for (i=n;i<len;++i) a[i]=0;
      for (i=0;i<m;++i) b[i]=t[i]==ch;
      for (i=m;i<len;++i) b[i]=0;
      fft(a,len,1),fft(b,len,1);
      for (i=0;i<len;++i) ans[i]=ans[i]+a[i]*b[i]*(ch^'*'?1:-1);
    }
    fft(ans,len,-1);
    for (i=0;i<len;++i) ans[i].x/=len; 
    for (i=m-1;i<n;++i) {
      ++zw[m-int(ans[i].x+(i==m-1?cnts[i]:cnts[i]-cnts[i-m])+cntt+.5)];
    }
    for (i=0;i<=m;++i) {
      i?zw[i]+=zw[i-1]:0;
      printf("%d\n",zw[i]);
    }
  }
}

谢谢大家.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值