2020年牛客第10场B-KMP Algorithm

本文探讨了一种解决近似周期字符串匹配问题的高效算法,通过将问题转化为多项式乘法,利用FFT和NTT技术,并结合字符串的近似周期性质,实现了O(26nlogn)复杂度的优化。文章详细介绍了算法思路,包括如何定义和利用Df和Dg数组,以及如何通过卷积计算得到最终结果。
摘要由CSDN通过智能技术生成

题意:定义两个相同长度为 n n n的字符串 s , t s,t s,t, m i s ( s , t ) = ∑ i = 1 i = n ( s [ i [ ! = t [ i ] ) mis(s,t)=\sum\limits_{i=1}^{i=n}(s[i[!=t[i]) mis(s,t)=i=1i=n(s[i[!=t[i])
定义字符串 s s s,若 s s s满足 m i s ( s [ 1 ] . . . s [ n − d ] , s [ d + 1 ] . . . s [ n ] ) ≤ d mis(s[1]...s[n-d],s[d+1]...s[n])\le d mis(s[1]...s[nd],s[d+1]...s[n])d
则该字符串为 nearly d-periodic,给你一个长度为 n n n的字符串 s s s和长度为 m m m的字符串 t t t,保证两个字符串都是 nearly d-periodic,求对任意 i ∈ [ 1 , n − m + 1 ] i∈[1,n-m+1] i[1,nm+1], m i s ( s [ i ] . . . s [ i + m − 1 ] , t [ 1 ] . . t [ m ] ) mis(s[i]...s[i+m-1],t[1]..t[m]) mis(s[i]...s[i+m1],t[1]..t[m]).


题解:
对于 i i i,我们可以把求不同转成总数减去相同的。
现在求相同:以下下标均从0开始。 ∗ * 为卷积, ⋅ \cdot 为乘
分别对不同字符求贡献。
下面仅对一种字符 s t st st展开讨论(其他都是一样的)
f [ j ] = ( s [ j ] = = s t ) f[j]=(s[j]==st) f[j]=(s[j]==st);
g [ j ] = ( t [ j ] = = s t ) g[j]=(t[j]==st) g[j]=(t[j]==st);
那么对于 i i i s t st st产生的贡献便是
∑ k = 0 k = m − 1 f [ i + k ] ⋅ g [ k ] \sum\limits_{k=0}^{k=m-1}f[i+k]\cdot g[k] k=0k=m1f[i+k]g[k];
因为对于同个位置,只有 s s s t t t都是 s t st st才能产生贡献。
这样好像看不出什么东西。
但如果把 t t t字符串翻转一下,那么 g g g数组同样也会翻转,即
g [ j ] = g [ m − 1 − j ] g[j]=g[m-1-j] g[j]=g[m1j]
那么贡献就变成
∑ k = 0 k = m − 1 f [ i + k ] ⋅ g [ m − 1 − k ] \sum\limits_{k=0}^{k=m-1}f[i+k]\cdot g[m-1-k] k=0k=m1f[i+k]g[m1k]
看着像啥?多项式相乘啊!
i + k + m − 1 − k = m − 1 + i i+k+m-1-k=m-1+i i+k+m1k=m1+i相对于特定的 i i i是一个定值
也就是说你做一下卷积后, ( f ∗ g ) ( m − 1 + i ) (f*g)(m-1+i) (fg)(m1+i)就是 s t st st的贡献了
大概你会马上想到什么 F F T , N T T FFT,NTT FFT,NTT啥的搞一下,但是这些都是 n l o g n nlogn nlogn的复杂度,而字符范围有26, n 是 1 e 6 n是1e6 n1e6,复杂度会变成 O ( 26 n l o g n ) O(26 nlog n) O(26nlogn)是肯定不能接受的
但是还有一个条件没有用, s , t s,t s,t都是 nearly d-periodic ,
也就是说( ∑ i = − ∞ + ∞ ( f [ i ] − f [ i + d ] ) ) ≤ d \sum\limits_{i=-\infty}^{+\infty}(f[i]-f[i+d]))\le d i=+(f[i]f[i+d]))d
那么我们便可以定义:
D f [ i ] = f [ i ] − f [ i + d ] Df[i]=f[i]-f[i+d] Df[i]=f[i]f[i+d]
D g [ i ] = g [ i ] − g [ i + d ] Dg[i]=g[i]-g[i+d] Dg[i]=g[i]g[i+d]
而且保证了 D f [ i ] 和 D g [ i ] 的 非 零 系 数 只 有 O ( d ) 项 Df[i]和Dg[i]的非零系数只有O(d)项 Df[i]Dg[i]O(d),而 d d d很小
将下标扩展到整数范围。
( D f ∗ D g ) i = ∑ u + v = i D f [ u ] ⋅ D g [ v ] (Df*Dg)i=\sum\limits_{u+v=i}Df[u]\cdot Dg[v] (DfDg)i=u+v=iDf[u]Dg[v]
接下来推公式
( D f ∗ D g ) i (Df*Dg)i (DfDg)i
= ∑ u + v = i ( f [ u ] − f [ u + d ] ) ( g [ v ] − g [ v + d ] ) =\sum\limits_{u+v=i}(f[u]-f[u+d])(g[v]-g[v+d]) =u+v=i(f[u]f[u+d])(g[v]g[v+d])
= ∑ u + v = i ( f [ u ] ⋅ g [ v ] − f [ u + d ] ⋅ g [ v ] − f [ u ] ⋅ g [ v + d ] + f [ u + d ] ⋅ g [ v + d ] =\sum\limits_{u+v=i}(f[u]\cdot g[v]-f[u+d]\cdot g[v]-f[u]\cdot g[v+d]+f[u+d]\cdot g[v+d] =u+v=i(f[u]g[v]f[u+d]g[v]f[u]g[v+d]+f[u+d]g[v+d]
= ∑ u + v = i f [ u ] ⋅ g [ v ] − ∑ x + y = i + d 2 ⋅ f [ x ] ⋅ g [ y ] + ∑ x + y = i + 2 d f [ x ] ⋅ g [ y ] =\sum\limits_{u+v=i}f[u]\cdot g[v]-\sum\limits_{x+y=i+d}2\cdot f[x]\cdot g[y]+\sum\limits_{x+y=i+2d}f[x]\cdot g[y] =u+v=if[u]g[v]x+y=i+d2f[x]g[y]+x+y=i+2df[x]g[y]
= ( f ∗ g ) i − 2 ⋅ ( g ∗ f ) ( i + d ) + ( g ∗ f ) ( i + 2 d ) =(f*g)i-2\cdot(g*f)(i+d)+(g*f)(i+2d) =(fg)i2(gf)(i+d)+(gf)(i+2d)

也就是说 ( f ∗ g ) i = ( D f ∗ D g ) i + 2 ( f ∗ g ) ( i + d ) − ( f ∗ g ) ( i + 2 d ) (f*g)i=(Df*Dg)i+2(f*g)(i+d)-(f*g)(i+2d) (fg)i=(DfDg)i+2(fg)(i+d)(fg)(i+2d)
那么对于字符 s t st st的贡献便是 ( f ∗ g ) ( m − 1 + i ) (f*g)(m-1+i) (fg)(m1+i)
对26个字符求一下和就是最终相同位置的数量 s a m e same same,然后 m − s a m e m-same msame就是答案了
如果你觉得还不错,点个赞再走呗,如果还有疑问(纠正),请指出。
代码(基本跟标程没啥区别,毕竟看着它理解的):

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define fi first
#define se second
#define mp make_pair
#define endl '\n'
const int MX=2e6+7;
using namespace std;
vector<pair<int,int>>v1[30],v2[30];//v1[i]存在字符i的f值,如f[v[i][0].first]=v[i][0].secon,v2存g的;
char s[MX],t[MX];
int main()
{
  ios::sync_with_stdio(0),cin.tie(0);
  int d;
  cin>>d;
  cin>>s>>t;
  int n=strlen(s),m=strlen(t);
  reverse(t,t+m);//令g[m-1-j]=g[j];
  //对于f[i]-f[i+d]=0部分,和g[i]-g[i+d]=0部分是无用的,对答案没有贡献,所以整个整数范围可能产生非零的只有[-d,n-1](对于f)和[-d,m-1](对于g)                       )
  for(int i=0;i<d;i++)v1[s[i]-'a'].pb(mp(i-d,-1)),v2[t[i]-'a'].pb(mp(i-d,-1));//存Df[-d...-1]和Dg[-d...-1]
  for(int i=0;i<n;i++)//存Df[0..n-1]
  {
      if(s[i]!=s[i+d])
      {
          v1[s[i]-'a'].pb(mp(i,1));//说明,f[i]=1,f[i+d]=0,所以Df[i]=1;
          if(i+d<n)v1[s[i+d]-'a'].pb(mp(i,-1));//说明f[i]=0,f[i+d]=1,Df[i]=-1
      }
  }
  for(int i=0;i<m;i++)//Dg[0...m-1]
  {
      if(t[i]!=t[i+d])
      {
          v2[t[i]-'a'].pb(mp(i,1));
          if(i+d<m)v2[t[i+d]-'a'].pb(mp(i,-1));
      }
  }
  vector<int>cnt(n+m,0);
  //求Df*Dg
    for(int i=0;i<26;i++)
      for(auto f:v1[i])
          for(auto g:v2[i])
              if(f.fi+g.fi>=m-1) cnt[f.fi+g.fi]+=f.se*g.se;
//
  vector<int>same(n+m+2*d,0);
  //从Df*Dg得到f*g
    for(int i=n+m-2;i>=m-1;i--)same[i]=cnt[i]+2*same[i+d]-same[i+2*d];
  
  for(int i=0;i<=n-m;i++)cout<<m-same[m-1+i]<<endl;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值