bzoj4259残缺的字符串FFT解题报告

4259: 残缺的字符串

Time Limit: 10 Sec   Memory Limit: 128 MB
Submit: 869   Solved: 208
[ Submit][ Status][ Discuss]

Description

很久很久以前,在你刚刚学习字符串匹配的时候,有两个仅包含小写字母的字符串A和B,其中A串长度为m,B串长度为n。可当你现在再次碰到这两个串时,这两个串已经老化了,每个串都有不同程度的残缺。
你想对这两个串重新进行匹配,其中A为模板串,那么现在问题来了,请回答,对于B的每一个位置i,从这个位置开始连续m个字符形成的子串是否可能与A串完全匹配?

Input

第一行包含两个正整数m,n(1<=m<=n<=300000),分别表示A串和B串的长度。
第二行为一个长度为m的字符串A。
第三行为一个长度为n的字符串B。
两个串均仅由小写字母和*号组成,其中*号表示相应位置已经残缺。

Output

第一行包含一个整数k,表示B串中可以完全匹配A串的位置个数。
若k>0,则第二行输出k个正整数,从小到大依次输出每个可以匹配的开头位置(下标从1开始)。

Sample Input

3 7
a*b
aebr*ob

Sample Output

2
1 5

HINT

Source

[ Submit][ Status][ Discuss]

HOME   Back

题目大意:
带*号字符串匹配(*可以配任意字母)
神题啊,没想到fft还可以干这种事情。
首先不考虑*,对于某个在第k位匹配的字符串,一定满足
sum(Ai+k-Bi)^2=0(小写字母为下标)
然后嘞,*那一位显然要强制转换为0,那么我们就令*那一位为0,然后只要满足
sum(Ai+k-Bi)^2 * Ai+k * Bi=0就可以了。
我们将上式化简,得到
sum(Ai+k^3 *Bi + Ai+k * Bi ^ 3 - 2 * Ai+k ^ 2 * Bi ^ 2)=0
我们要将上式转化为若干个多项式乘法,显然只和Ai+k和Bi有关。
考虑到普通多项式乘法把Ai+k*Bi项的贡献统计到2i+k位上
而题设希望所有Ai+k和Bi相关计算都统计到同一位上
只需对B序列进行适当转化,使得所有的i+k和i位统计到同一位上即可
假设转化后对应关系为f(i)
那么有对于任意的i,i+f(i)=a(a为定值)
于是有f(i)=a-i
至于a具体是什么,看个人的具体喜好,反正我是令a=n,也就是把整个B串反转了。
这样的话,只需要进行三次多项式乘法之后,检验符号条件的为0的项即可,问题就圆满地解决了。
大体思路过程:字符串匹配->数学表达式->多项式乘法,其中有通过贡献考虑问题的和模型转化的类比思想
代码贼舒服

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<cmath>
using namespace std;
const int N = 6e5 + 6e3;
const double pi = acos(-1.0);
struct cp {
    double r, i;
    cp(double real = 0, double imag = 0) : r(real), i(imag) {}
    cp operator * (cp a) {return cp(r * a.r - i * a.i, i * a.r + r * a.i);}
    cp operator * (double a) {return cp(r * a, i * a);}
    cp operator + (cp a) {return cp(r + a.r, i + a.i);}
    cp operator - (cp a) {return cp(r - a.r, i - a.i);}
}A[N], B[N], C[N];
int R[N], a[N], b[N], ans[N], bin[40], top, m, n, L, k;
char sa[N], sb[N];
  
void FFT(cp *F, int f) {
    for(int i = 0;i < k; ++i) if(i < R[i]) swap(F[i], F[R[i]]);
    for(int i = 1;i < k; i <<= 1) {
        cp wn(cos(pi / i), f * sin(pi / i));
        for(int j = 0; j < k; j += (i << 1)) {
            cp w(1, 0);
            for(int l = 0;l < i; ++l, w = w * wn) {
                cp x = F[j + l], y = w * F[j + l + i];
                F[j + l] = x + y; F[j + l + i] = x - y;
            }
        }
    }
    if(!(~f)) for(int i = 0;i < k; ++i) F[i].r /= k;
}
  
int mul(int x, int cnt) {int ret = 1; while(cnt--) ret = ret * x; return ret;}
void work(int p) {
    for(int i = 0;i < k; ++i) A[i] = cp(mul(a[i], p), 0), B[i] = cp(mul(b[i], 4 - p), 0);
    FFT(A, 1); FFT(B, 1); bool f = p == 2;
    if(f) for(int i = 0;i < k; ++i) C[i] = C[i] - A[i] * B[i] * 2;
    else for(int i = 0;i < k; ++i) C[i] = C[i] + A[i] * B[i];
      
}
  
int main() {
    scanf("%d%d%s%s", &m, &n, sa, sb);
    for(int i = 0;i < (m >> 1); ++i) swap(sa[i], sa[m - i - 1]);
    for(int i = 0;i < m; ++i) a[i] = sa[i] == '*' ? 0 : sa[i] - 'a' + 1;
    for(int i = 0;i < n; ++i) b[i] = sb[i] == '*' ? 0 : sb[i] - 'a' + 1;
    for(k = 1;k < n + m; k <<= 1, ++L) ; 
    for(int i = 0;i < k; ++i) R[i] = (R[i >> 1] >> 1) | ((i & 1) << L - 1);
    for(int i = 1;i <= 3; ++i) work(i);
    FFT(C, -1);
    for(int  i = m - 1;i < n; ++i) if(C[i].r < 0.5) ans[++top] = i - m + 2;
    printf("%d\n", top);
    for(int i = 1;i <= top; ++i) printf("%d ", ans[i]);
    puts("");
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值