luogu P4173 残缺的字符串 FFT

温馨提示:倘若下角标看不清的话您可以尝试放大。

倘若没有通配符的话可以用KMP搞一搞。

听巨佬说通配符可以用FFT搞一搞。

我们先考虑一下没有通配符的怎么搞。我们设a=1,b=2,...,然后我们构造一个这样的函数\(\displaystyle P_x=\sum_{i=0}^{m-1}(A_i-B_{x-m+1+i})^2\),但且仅当A和B在x的位置上匹配完成的时候$P_x $为0。至于为什么是平方,主要是为了防止正数和负数相互抵消。

至于通配符,我们设它为0,我们尝试重新构造一下\(\displaystyle P_x=\sum_{i=0}^{m-1}(A_i-B_{x-m+1+i})^2A_iB_{x-m+1+i}\),这样我们就能满足"通配"这一条件了。

那我们怎么快速求解呢?我们将式子先展开一下,

\(\displaystyle P_x=\sum_{i=0}^{m-1}(A_i^3B_{x-m+1+i}-A_i^2B_{x-m+1+i}^2+A_iB_{x-m+1+i}^3)\)

\(\displaystyle=\sum_{i=0}^{m-1}A_i^3B_{x-m+1+i}-\sum_{i=0}^{m-1}A_i^2B_{x-m+1+i}^2+\sum_{i=0}^{m-1}A_iB_{x-m+1+i}^3\)

还是老方法,我们尝试将A翻转一下设\(A_i=C_{m-i-1}\),带进原来的式子。

\(\displaystyle=\sum_{i=0}^{m-1}C_{m-i-1}^3B_{x-m+1+i}-\sum_{i=0}^{m-1}C_{m-i-1}^2B_{x-m+1+i}^2+\sum_{i=0}^{m-1}C_{m-i-1}B_{x-m+1+i}^3\)

发现了什么吗?C和B的下角标之和等于x,所以我们换一种写法。

\(\displaystyle=\sum_{i+j=x}C_{i}^3B_{j}-\sum_{i+j=x}C_{i}^2B_{j}^2+\sum_{i+j=x}C_{i}B_{j}^3\)

是不是好看了很多?这很明显是一个卷积...

剩下的FFT一波带走就可以了。

#include<bits/stdc++.h>
#define LL long long
#define DB double
using namespace std;
int n, m, lim;
const int N = 1200010;
const DB PI = acos(-1);
int r[N], cnt[N];
DB a[N], b[N];
char s1[N], s2[N];
struct xu
{
    DB x, y;
    xu(DB X = 0, DB Y = 0) {x = X, y = Y;}
    friend xu operator +(const xu &a, const xu &b)
    {return (xu) {a.x + b.x, a.y + b.y};}
    friend xu operator -(const xu &a, const xu &b)
    {return (xu) {a.x - b.x, a.y - b.y};}
    friend xu operator *(const xu &a, const xu &b)
    {return (xu) {a.x*b.x - a.y*b.y, a.x*b.y + a.y*b.x};}
} A[N], B[N], ans[N];
void FFT(xu *A, int lim, int opt)
{
    for (int i = 0; i < lim; ++i)
        r[i] = (r[i >> 1] >> 1) | ((i & 1) ? (lim >> 1) : 0);
    for (int i = 0; i < lim; ++i)
        if (i < r[i])swap(A[i], A[r[i]]);
    int len;
    xu wn, w, x, y;
    for (int mid = 1; mid < lim; mid <<= 1)
    {
        len = mid << 1;
        wn = (xu) {cos(PI / mid), opt*sin(PI / mid)};
        for (int j = 0; j < lim; j += len)
        {
            w = (xu) {1, 0};
            for (int k = j; k < j + mid; ++k, w = w * wn)
            {
                x = A[k]; y = A[k + mid] * w;
                A[k] = x + y; A[k + mid] = x - y;
            }
        }
    }
    if (opt == 1)return;
    for (int i = 0; i < lim; ++i)A[i].x /= lim;
}
int main()
{
    cin >> n >> m;
    scanf("%s", s1); scanf("%s", s2);
    lim = 1;
    while (lim <= (n + m))lim <<= 1;
    reverse(s1, s1 + n);
    for (int i = 0; i < n; ++i)a[i] = (s1[i] == '*') ? 0 : s1[i] - 'a' + 1;
    for (int i = 0; i < m; ++i)b[i] = (s2[i] == '*') ? 0 : s2[i] - 'a' + 1;

    for (int i = 0; i < lim; ++i)A[i] = (xu) {a[i]*a[i]*a[i], 0}, B[i] = (xu) {b[i], 0};
    FFT(A, lim, 1); FFT(B, lim, 1);
    for (int i = 0; i < lim; ++i)ans[i] = ans[i] + A[i] * B[i];

    for (int i = 0; i < lim; ++i)A[i] = (xu) {a[i], 0}, B[i] = (xu) {b[i]*b[i]*b[i], 0};
    FFT(A, lim, 1); FFT(B, lim, 1);
    for (int i = 0; i < lim; ++i)ans[i] = ans[i] + A[i] * B[i];

    for (int i = 0; i < lim; ++i)A[i] = (xu) {a[i]*a[i], 0}, B[i] = (xu) {b[i]*b[i], 0};
    FFT(A, lim, 1); FFT(B, lim, 1);
    for (int i = 0; i < lim; ++i)ans[i] = ans[i] - A[i] * B[i] * (xu) {2, 0};
    FFT(ans, lim, -1);

    for (int i = n - 1; i < m; ++i)
        if (!(int)(ans[i].x + 0.5))cnt[++cnt[0]] = i - n + 2;
    cout << cnt[0] << endl;
    for (int i = 1; i <= cnt[0]; ++i)printf("%d ", cnt[i]);
    return 0;
}

原文地址:https://www.cnblogs.com/wljss/p/12020196.html

时间: 2024-08-28 04:19:30

luogu P4173 残缺的字符串 FFT的相关文章

Luogu P4173 残缺的字符串

P4173 残缺的字符串 FFT在字符串匹配中的应用. 能解决大概这种问题: 给定长度为\(m\)的A串,长度为\(n\)的B串.问A串在B串中的匹配数 我们设一个函数(下标从\(0\)开始) \(C(x,y) =A(x)- B(y)\),若为0,表示B串中以第\(y\)个字符结尾的字符可以与A串中以\(x\)节为结尾的字符可以匹配 \(P(x) = \sum_{i = 0}^{m - 1}C(i,x - m + i + 1)\) 但是很遗憾当\(P(x)\),等于零时,只能够说明上述子串的字符

[Luogu P4173]残缺的字符串 ( 数论 FFT)

题面 传送门:洛咕 Solution 这题我写得脑壳疼,我好菜啊 好吧,我们来说正题. 这题.....emmmmmmm 显然KMP类的字符串神仙算法在这里没法用了. 那咋搞啊(或者说这题和数学有半毛钱关系啊) 我们考虑把两个字符相同强行变为一个数学关系,怎么搞呢? 考虑这题是带通配符的,我们可以这样设: \(C(x,y)=(A[x]-B[y])^2*A[x]*B[y]\) 因此,我们可以看出两个字符一样当且仅当\(C(x,y)=0\) 因此,我们再设一个函数\(P(x)\)表示\(B\)串以第\

P4173 残缺的字符串 fft

题意:给你两个字符串,问你第一个在第二个中出现过多少次,并输出位置,匹配时是模糊匹配*可和任意一个字符匹配 题解:fft加速字符串匹配; 假设上面的串是s,s长度为m,下面的串是p,p长度为n,先考虑没有*的情况那么\(\sum_{j=1}^m(s_{i+j}-p_j)^2=0\)就表示能够从i开始匹配,现在考虑有*的情况,我们只需要让有*的和任意字符匹配即可,那么把公式变成\(\sum_{j=1}^m(s_{i+j}-p_j)^2*s_{i+j}*p_j)=0\),但是fft正向匹配太慢了,我

P4173 残缺的字符串(FFT)

[Luogu4173] 题解 \(1.\)定义匹配函数 \(2.\)定义完全匹配函数 \(3.\)快速计算每一位的完全匹配函数值 #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cmath> #define debug(...) fprintf(stderr,__VA_ARGS__) #define Debug(x) cout&l

P4173 残缺的字符串

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

BZOJ 4259 残缺的字符串 ——FFT

[题目分析] 同bzoj4503. 只是精度比较卡,需要试一试才能行O(∩_∩)O 用过long double,也加过0.4.最后发现判断的时候改成0.4就可以了 [代码] #include <cmath> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; #define maxn 1200005 #

luogu P3709 大爷的字符串题

二次联通门 : luogu P3709 大爷的字符串题 /* luogu P3709 大爷的字符串题 莫队 看了半天题目 + 题解 才弄懂了要求什么... 维护两个数组 一个记录数字i出现了几次 一个记录出现了i次的有几个数.. */ #include <algorithm> #include <cstdlib> #include <cstdio> #include <cmath> #define Max 200090 void read (int &

bzoj 4259 4259: 残缺的字符串【FFT】

和bzoj 4503 https://www.cnblogs.com/lokiii/p/10032311.html 差不多,就是再乘上一个原串字符 有点卡常,先在点值下算最后一起IDFT #include<iostream> #include<cstdio> #include<cstring> #include<cmath> using namespace std; const int N=1100005; int n,m,bt,lm,re[N],tot;

【BZOJ4259】 残缺的字符串

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