@bzoj - [email protected] 残缺的字符串

目录

  • @[email protected]
  • @[email protected]
  • @accepted [email protected]
  • @[email protected]

@[email protected]

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

原题链接。

@[email protected]

据说已经成了套路题。
以下内容中假定以 0 为字符串下标起点。

考虑没有通配符的情况,B 中第 j 个位置开头的串能否与 A 匹配。实际上是计算式子 \(\sum_{i=0}^{m-1}[A_i\not = B_{i+j}]\) 是否为 0。

注意到这个注意匹配的形式很像卷积。不过别急,我们先把其拆成数值计算的形式:上式为 0 等价于 \(\sum_{i=0}^{m-1}(A_i - B_{i+j})^2\)。
由完全平方的非负性可以得到等价性。

有通配符怎么办?A, B 其中一个为通配符,构造出来的式子就应该等于 0。
不妨令通配符等于 0,则计算 \(\sum_{i=0}^{m-1}(A_i - B_{i+j})^2\times A_i \times B_{i+j}\) 是否为 0 即可。

将完全平方拆开,再把 A 翻转一下就可以 fft 算了。

@accepted [email protected]

#include <cmath>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;

#define double long double

const int MAXN = 300000*4;
const double PI = acos(-1);
const double EPS = 0.5;

struct complex{
    double r, i; complex() {}
    complex(double _r, double _i) : r(_r), i(_i) {}
    friend complex operator + (complex A, complex B) {
        return complex(A.r + B.r, A.i + B.i);
    }
    friend complex operator - (complex A, complex B) {
        return complex(A.r - B.r, A.i - B.i);
    }
    friend complex operator * (complex A, complex B) {
        return complex(A.r*B.r - A.i*B.i, A.i*B.r + B.i*A.r);
    }
    friend complex operator / (complex A, double k) {
        return complex(A.r / k, A.i / k);
    }
};

void fft(complex *A, int n, int type) {
    for(int i=0,j=0;i<n;i++) {
        if( i < j ) swap(A[i], A[j]);
        for(int k=(n>>1);(j^=k)<k;k>>=1);
    }
    for(int s=2;s<=n;s<<=1) {
        int t = (s >> 1);
        complex u = complex(cos(type*2*PI/s), sin(type*2*PI/s));
        for(int j=0;j<n;j+=s) {
            complex p = complex(1, 0);
            for(int k=0;k<t;k++,p=p*u) {
                complex x = A[j+k], y = A[j+k+t]*p;
                A[j+k] = x + y, A[j+k+t] = x - y;
            }
        }
    }
    if( type == -1 ) {
        for(int i=0;i<n;i++)
            A[i] = A[i] / n;
    }
}
int length(int n) {
    int len; for(len = 1; len < n; len <<= 1);
    return len;
}

double A[MAXN + 5], B[MAXN + 5];
complex f[MAXN + 5], g[MAXN + 5], h[MAXN + 5];

char sa[MAXN + 5], sb[MAXN + 5];

vector<int>ans;

int main() {
    int m, n; scanf("%d%d", &m, &n);
    scanf("%s%s", sa, sb);
    for(int i=0;i<m;i++) A[m-1-i] = (sa[i] == '*' ? 0 : sa[i] - 'a' + 1);
    for(int i=0;i<n;i++) B[i] = (sb[i] == '*' ? 0 : sb[i] - 'a' + 1);
    int len = length(n + m - 1);

    for(int i=0;i<len;i++) f[i] = g[i] = complex(0, 0);
    for(int i=0;i<m;i++) f[i].r = A[i]*A[i]*A[i];
    for(int i=0;i<n;i++) g[i].r = B[i];
    fft(f, len, 1), fft(g, len, 1);
    for(int i=0;i<len;i++) h[i] = h[i] + f[i]*g[i];

    for(int i=0;i<len;i++) f[i] = g[i] = complex(0, 0);
    for(int i=0;i<m;i++) f[i].r = A[i]*A[i];
    for(int i=0;i<n;i++) g[i].r = B[i]*B[i];
    fft(f, len, 1), fft(g, len, 1);
    for(int i=0;i<len;i++) h[i] = h[i] - f[i]*g[i] - f[i]*g[i];

    for(int i=0;i<len;i++) f[i] = g[i] = complex(0, 0);
    for(int i=0;i<m;i++) f[i].r = A[i];
    for(int i=0;i<n;i++) g[i].r = B[i]*B[i]*B[i];
    fft(f, len, 1), fft(g, len, 1);
    for(int i=0;i<len;i++) h[i] = h[i] + f[i]*g[i];

    fft(h, len, -1);
    for(int i=m-1;i<n;i++) {
//      printf("%d : %.6f %.6f\n", i, h[i].r, h[i].i);
        if( fabs(h[i].r) < EPS ) ans.push_back(i-m+2);
    }
    printf("%d\n", (int)ans.size());
    for(int i=0;i<ans.size();i++)
        printf("%d%c", ans[i], i + 1 == ans.size() ? '\n' : ' ');
}

@[email protected]

fft 一大缺点就是精度损失比较大,eps 太小就会误判。。。

不过由于这道题 fft 出来肯定是整数,所以 eps 即使调到 0.5 也没有啥大问题。

感觉匹配问题如果常规字符串算法不能做,往往就可以转成卷积然后 fft 做。

原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/12219225.html

时间: 2024-08-24 07:21:37

@bzoj - [email protected] 残缺的字符串的相关文章

@bzoj - [email&#160;protected] [POI2015] Kinoman

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 共有 m 部电影,第 i 部电影的好看值为 w[i]. 在 n 天之中每天会放映一部电影,第 i 天放映的是第 f[i] 部. 你可以选择 l, r (1 <= l <= r <= n) ,并观看第 l, l+1, -, r 天内所有的电影. 最大化观看且仅观看过一次的电影的好

@bzoj - [email&#160;protected] [Noi2014]动物园

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 近日,园长发现动物园中好吃懒做的动物越来越多了.例如企鹅,只会卖萌向游客要吃的.为了整治动物园的不良风气,让动物们凭自己的真才实学向游客要吃的,园长决定开设算法班,让动物们学习算法. 某天,园长给动物们讲解KMP算法. 园长:"对于一个字符串S,它的长度为L.我们可以在O(L)的时间内

@bzoj - [email&#160;protected] [Poi2011]Lightning Conductor

目录 @[email protected] @[email protected] @part - [email protected] @part - [email protected] @part - [email protected] @accepted [email protected] @version - [email protected] @version - [email protected] @[email protected] @[email protected] 已知一个长度为

@bzoj - [email&#160;protected] [POI2014]Hotel加强版

目录 @[email protected] @[email protected] @part - [email protected] @part - [email protected] @accepted [email protected] @[email protected] @[email protected] 给定一棵树,求无序三元组 (a, b, c) 的个数,使得 dis(a, b) = dis(b, c) = dis(c, a),且 a ≠ b, b ≠ c, c ≠ a. inpu

@bzoj - [email&#160;protected] 旅行规划

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 请你维护一个序列,支持两种操作: (1)某个区间 [x, y] 内的数同时加上一个增量 k. (2)询问某一个区间 [x, y] 中从 1 开始的最大前缀和. input 第一行给出一个整数 n.n <= 100000.接下来一行 n 个整数表示序列的初始值. 第三行给出一个整数 m,

@bzoj - [email&#160;protected] Hard Nim

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] n 堆石子,每堆石子的数量是不超过 m 的一个质数. 两个人玩 nim 游戏,问使后手必胜的初始局面有多少种. 模 10^9 + 7. input 多组数据.数据组数 <= 80. 每组数据一行两个正整数,n 和 m.1 <= n <= 10^9, 2 <= m <

@bzoj - [email&#160;protected] [Cqoi2016]伪光滑数

目录 @description@ @[email protected] @version - [email protected] @version - [email protected] @accepted [email protected] @[email protected] @description@ 若一个大于 1 的整数 M 的质因数分解有 k 项,其最大的质因子为 \(A_k\),并且满足 \(A_k^k \le N\),\(A_k < 128\),我们就称整数 M 为 N - 伪光

@bzoj - [email&#160;protected] [POI2015] Wilcze do?y

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 给定一个长度为 n 的序列,你有一次机会选中一段连续的长度不超过 d 的区间,将里面所有数字全部修改为 0. 请找到最长的一段连续区间,使得该区间内所有数字之和不超过 p . input 第一行包含三个整数 n, p, d (1 <= d <= n <= 2000000,0 &

@bzoj - [email&#160;protected] [POI2015] Pustynia

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 给定一个长度为 n 的正整数序列 a,每个数都在 1 到 10^9 范围内. 告诉你其中 s 个数,并给出 m 条信息,每条信息包含三个数 l, r, k 以及 k 个正整数,表示 a[l], a[l+1], ..., a[r-1], a[r] 里这 k 个数中的任意一个都比任意一个剩