• @bzoj



    @description@

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

    原题链接。

    @solution@

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

    考虑没有通配符的情况,B 中第 j 个位置开头的串能否与 A 匹配。实际上是计算式子 (sum_{i=0}^{m-1}[A_i ot = 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 imes A_i imes B_{i+j}) 是否为 0 即可。

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

    @accepted code@

    #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
    ", i, h[i].r, h[i].i);
    		if( fabs(h[i].r) < EPS ) ans.push_back(i-m+2);
    	}
    	printf("%d
    ", (int)ans.size());
    	for(int i=0;i<ans.size();i++)
    		printf("%d%c", ans[i], i + 1 == ans.size() ? '
    ' : ' ');
    }
    

    @details@

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

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

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

  • 相关阅读:
    双系统卸载linux和装双系统的方法
    linux中使用vim编译C++程序
    存储器管理之页面置换算法
    Python中open文件的各种打开模式
    RAL调用
    分布式系统事务一致性解决方案
    消息队列设计
    nmq消息队列解析
    分布式session的实现
    分布式系统常用思想和技术总结 (入门很清楚)
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/12219225.html
Copyright © 2020-2023  润新知