P4173 残缺的字符串 字符串匹配 快速傅里叶变换fft

若字符串中不含通配符如何进行匹配

A A A是模式串,长度 m m m B B B是原串,长度 n n n

定义匹配函数 P ( x ) = ∑ i = 0 m − 1 [ A ( i ) − B ( x − m + i + 1 ) ] P(x)=\sum_{i=0}^{m-1}[A(i)-B(x-m+i+1)] P(x)=i=0m1[A(i)B(xm+i+1)]

P ( x ) = 0 P(x)=0 P(x)=0时,字符串 B B B的以第 x x x为结束的连续 m m m位,与 A A A完全匹配

因为上述匹配函数是加和的形式进行判断,无法判断顺序

考虑将匹配函数进行平方,消除顺序的影响

P ( x ) = ∑ i = 0 m − 1 [ A ( i ) − B ( x − m + i + 1 ) ] 2 P(x)=\sum_{i=0}^{m-1}[A(i)-B(x-m+i+1)]^2 P(x)=i=0m1[A(i)B(xm+i+1)]2

上述函数无法使用 f f t fft fft进行求解,将 A A A逆置

P ( x ) = ∑ i = 0 m − 1 [ S ( m − i − 1 ) − B ( x − m + i + 1 ) ] 2 P(x)=\sum_{i=0}^{m-1}[S(m-i-1)-B(x-m+i+1)]^2 P(x)=i=0m1[S(mi1)B(xm+i+1)]2

拆项

P ( x ) = ∑ i = 0 m − 1 S ( m − i − 1 ) 2 + ∑ i = 0 m − 1 B ( x − m + i + 1 ) 2 − 2 ∑ i = 0 m − 1 S ( m − i − 1 ) B ( x − m + i + 1 ) P(x)=\sum_{i=0}^{m-1}S(m-i-1)^2+\sum_{i=0}^{m-1}B(x-m+i+1)^2-2\sum_{i=0}^{m-1}S(m-i-1)B(x-m+i+1) P(x)=i=0m1S(mi1)2+i=0m1B(xm+i+1)22i=0m1S(mi1)B(xm+i+1)

观察最后一项: m − i − 1 + x − m + i + 1 = x m-i-1+x-m+i+1=x mi1+xm+i+1=x

P ( x ) = ∑ i = 0 m − 1 S ( i ) 2 + ∑ i = 0 m − 1 B ( i ) 2 − 2 ∑ i + j = x m − 1 S ( i ) B ( j ) P(x)=\sum_{i=0}^{m-1}S(i)^2+\sum_{i=0}^{m-1}B(i)^2-2\sum_{i+j=x}^{m-1}S(i)B(j) P(x)=i=0m1S(i)2+i=0m1B(i)22i+j=xm1S(i)B(j)

题意

给你模式串和原串,串中存在通配符*,输出原串中可以和模式串完全匹配的每个开头位置。

通过上述对基本字符串的匹配,应该对如何用 f f t fft fft有了简单了解。

带有通配符??

定义匹配函数
P ( x ) = ∑ i = 0 m − 1 [ A ( i ) − B ( x − m + i + 1 ) ] 2 A ( i ) B ( x − m + i + 1 ) P(x)=\sum_{i=0}^{m-1}[A(i)-B(x-m+i+1)]^2A(i)B(x-m+i+1) P(x)=i=0m1[A(i)B(xm+i+1)]2A(i)B(xm+i+1)

S S S A A A的翻转: S ( i ) = A ( m − i − 1 ) S(i)=A(m-i-1) S(i)=A(mi1)

P ( x ) = ∑ i = 0 m − 1 [ S ( m − i − 1 ) − B ( x − m + i + 1 ) ] 2 S ( m − i − 1 ) B ( x − m + i + 1 ) P(x)=\sum_{i=0}^{m-1}[S(m-i-1)-B(x-m+i+1)]^2S(m-i-1)B(x-m+i+1) P(x)=i=0m1[S(mi1)B(xm+i+1)]2S(mi1)B(xm+i+1)

展开化简得到

f i = ∑ i + j = x S ( i ) 3 B ( j ) − 2 ∑ i + j = x S ( i ) 2 B ( j ) 2 + ∑ i + j = x S ( i ) B ( j ) 3 f_i=\sum_{i+j=x}S(i)^3B(j)-2\sum_{i+j=x}S(i)^2B(j)^2+\sum_{i+j=x}S(i)B(j)^3 fi=i+j=xS(i)3B(j)2i+j=xS(i)2B(j)2+i+j=xS(i)B(j)3

使用 f f t fft fft计算上述柿子,得到答案。

代码

#include <iostream>
#include <string>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <queue>
#include <iomanip>
#include <map>
#include <cstdio>
#include <stack>
#include <set>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int ,int > pii;
#define endl '\n'
ll gcd(ll a, ll b){
    return b == 0 ? a : gcd(b, a % b);
}
void input(){
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
}
inline int read(){
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
	while (c>='0'&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return x*f;
}
const int N = 2e6+10, M = N * 2, inf = 1e8;
const double PI = acos(-1);
int rev[N], bit, tot;
struct Complex{
    double x, y;
    Complex operator+ (const Complex& t) const { return {x + t.x, y + t.y}; }
    Complex operator- (const Complex& t) const{ return {x - t.x, y - t.y}; }
    Complex operator* (const Complex& t) const{ return {x * t.x - y * t.y, x * t.y + y * t.x}; }
}a[N], b[N], p[N];
void fft(Complex a[], int inv){
    for(int i = 0; i < tot; i++)
        if(i < rev[i]) swap(a[i], a[rev[i]]);
    for(int mid = 1; mid < tot; mid <<= 1){
        Complex w1 = Complex({cos(PI/mid), inv * sin(PI/mid)});
        for(int i = 0; i < tot; i += mid * 2){
            Complex wk = Complex({1, 0});
            for(int j = 0; j < mid; j ++, wk = wk * w1){
                Complex x = a[i + j], y = wk * a[i + j + mid];
                a[i + j] = x + y, a[i + j + mid] = x - y;
            }
        }
    }
}
int n, m;
double A[N], B[N];
char s1[N], s2[N];
int main(){
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    cin>>m>>n>>s1>>s2;
    while((1 << bit) < n + m + 1) bit++;
    tot = 1 << bit;
    for(int i = 0; i < tot; i++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (bit - 1));
    for(int i = 0; i < m; i++) if(s1[i] != '*') A[i] = s1[i] - 'a' + 1;
    for(int i = 0; i < n; i++) if(s2[i] != '*') B[i] = s2[i] - 'a' + 1;
    reverse(A, A + m);

    for(int i = 0; i < m; i++) a[i] = {A[i]*A[i]*A[i], 0};
    for(int i = 0; i < n; i++) b[i] = {B[i], 0};
    fft(a, 1), fft(b, 1);
    for(int i = 0; i < tot; i++) p[i] = p[i] + a[i] * b[i];
    for(int i = 0; i < tot; i++) a[i] = {0, 0}, b[i] = {0, 0};

    for(int i = 0; i < m; i++) a[i] = {A[i]*A[i], 0};
    for(int i = 0; i < n; i++) b[i] = {B[i]*B[i], 0};
    fft(a, 1), fft(b, 1);
    Complex f2 = {2, 0};
    for(int i = 0; i < tot; i++) p[i] = p[i] - a[i] * b[i] * f2;
    for(int i = 0; i < tot; i++) a[i] = {0, 0}, b[i] = {0, 0};

    for(int i = 0; i < m; i++) a[i] = {A[i], 0};
    for(int i = 0; i < n; i++) b[i] = {B[i]*B[i]*B[i], 0};
    fft(a, 1), fft(b, 1);
    for(int i = 0; i < tot; i++) p[i] = p[i] + a[i] * b[i];
    fft(p, -1);

    vector<int> v;
    for(int i = m - 1; i < n; i++)
        if(!(int)(p[i].x/(double)tot+0.5)) v.push_back(i-m+2);
    cout<<v.size()<<endl;
    for(auto i : v) cout<<i<<" ";
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值