zoukankan      html  css  js  c++  java
  • BZOJ.4503.两个串(FFT/bitset)

    题目链接

    (Description)

    给定两个字符串S和T,求T在S中出现了几次,以及分别在哪些位置出现。T中可能有'?'字符,这个字符可以匹配任何字符。
    (|S|,|T|leq 10^5)

    (Solution)

    FFT:

    https://www.cnblogs.com/cjyyb/p/8798446.html

    显然我们可以同CF528D一样枚举(26)个字符然后跑(FFT)。但字符集太大了,复杂度是(O(26nlog n)),以(FFT)的常数肯定GG。(然而CF上一道题(n)更大但(36nlog n)(FFT)还跑的非常轻松,这就是差距么...)

    考虑没有通配符时能怎么做(不考虑(KMP))。
    把每个字符(asim z)映射到(1sim26),那么(S_i,T_i)匹配了就是(S_i=T_i)。考虑(S,T)做差。但是每个位置需要有(|T|)个值求和来表示匹配(T)串的情况,就算不匹配正负相加也可能变成(0)
    所以考虑平方,即若(f(x)=sum_{i=0}^{|T|-1}(S_{x+i}-T_i)^2=0),那么(x)位置匹配了(T)
    把平方和拆开,就可以得到两个常数项和一个(sum_{i=0}^{|T|-1}S_{x+i}T_i),把(T)反转后就可以(FFT)了。

    如果有通配符呢?考虑如果(T_i)是通配符,怎么让它不产生影响,也就是贡献是(0)
    那么令通配符(T_i=0),外面再乘个(T_i)就可以了。即$$egin{aligned}f(x)&=sum_{i=0}^{|T|-1}(S_{x+i}-T_i)^2T_i&=sum_{i=0}^{|T|-1}S_{x+i}^2T_i-2sum_{i=0}^{|T|-1}S_{x+i}T_i^2+sum_{i=0}^{|T|-1}T_i^3end{aligned}$$

    第三部分是个常数,前两部分可以分别(FFT)求出来。

    //22280kb	3076ms(这慢的...)
    #include <cmath>
    #include <cctype>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    //#define gc() getchar()
    #define MAXIN 1000000
    #define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++)
    typedef long long LL;
    const int N=(1<<18)+5;
    const double PI=acos(-1);
    
    int S[N],T[N],rev[N];
    char IN[MAXIN],*SS=IN,*TT=IN;
    struct Complex
    {
    	double x,y;
    	Complex(double x=0,double y=0):x(x),y(y) {}
    	Complex operator +(const Complex &a) {return Complex(x+a.x, y+a.y);}
    	Complex operator -(const Complex &a) {return Complex(x-a.x, y-a.y);}
    	Complex operator *(const Complex &a) {return Complex(x*a.x-y*a.y, x*a.y+y*a.x);}
    }A[N],B[N],C[N],D[N];
    
    void FFT(Complex *a,int lim,int opt)
    {
    	for(int i=1; i<lim; ++i) if(i<rev[i]) std::swap(a[i],a[rev[i]]);
    	for(int i=2; i<=lim; i<<=1)
    	{
    		int mid=i>>1; Complex Wn(cos(PI/mid),opt*sin(PI/mid));
    		for(int j=0; j<lim; j+=i)
    		{
    			Complex w(1,0),t;
    			for(int k=j; k<j+mid; ++k,w=w*Wn)
    				a[k+mid]=a[k]-(t=a[k+mid]*w), a[k]=a[k]+t;
    		}
    	}
    	if(opt==-1) for(int i=0; i<lim; ++i) a[i].x/=lim;
    }
    
    int main()
    {
    	static int pos[N];
    
    	register char c;
    	int n=0; while(isalpha(c=gc())) S[n++]=c-96;
    	int m=0; while(isalpha(c=gc())||c=='?') T[m++]=c=='?'?0:c-96;
    	std::reverse(T,T+m);//!
    	for(int i=0; i<n; ++i) A[i]=Complex(S[i]*S[i],0),C[i]=Complex(S[i]<<1,0);
    	int sumT=0;
    	for(int i=0,t; i<m; ++i) B[i]=Complex(t=T[i],0),D[i]=Complex(t*t,0),sumT+=t*t*t;
    
    	int lim=1,l=-1;
    	while(lim<=n+m-2) lim<<=1,++l;//n+m-2就可以了...~~有点想不通...~~n-1次和m-1次咯
    	for(int i=1; i<lim; ++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<l);
    	FFT(A,lim,1), FFT(B,lim,1), FFT(C,lim,1), FFT(D,lim,1);
    	for(int i=0; i<lim; ++i) A[i]=A[i]*B[i]-C[i]*D[i];//可以直接在这里计算然后只IDFT一次啊(反正都是多项式)
    	FFT(A,lim,-1);
    
    	int ans=0;
    	for(int i=0; i<=n-m; ++i) if(!(int)(A[m+i-1].x+0.5+sumT)) pos[++ans]=i;
    	printf("%d
    ",ans);
    	for(int i=1; i<=ans; printf("%d
    ",pos[i++]));
    
    	return 0;
    }
    

    bitset:

    可以用bitset暴力匹配。
    维护bitset<N> Ans表示每个位置能否匹配(T)串。枚举(T)中的字符(T_j),维护(Ans_i)是否能够匹配(T_j),也就是(S_{i+j})处是否是(T_j)
    可以用(26)bitset<N> Pos[26]得到每种字符在(S)的哪些位置上出现过(出现过的位置设为(1))。
    然后枚举(T_j)(jin[0,T|))的时候,(Ans_i)每次都与上(Pos_{T_j})右移(j)位就可以了。(若是通配符就不用再与了)(注意是右移)
    最后若(Ans_i)还是(1),则(i)位置匹配(T)串。

    复杂度(O(frac{nm}{w}))

    //2744kb	1372ms
    #include <cctype>
    #include <cstdio>
    #include <bitset>
    #include <algorithm>
    //#define gc() getchar()
    #define MAXIN 1000000
    #define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++)
    const int N=1e5+3;
    
    char S[N],T[N];
    std::bitset<N> Ans,pos[26];
    char IN[MAXIN],*SS=IN,*TT=IN;
    
    int main()
    {
    	static int p[N];
    
    	register char c;
    	int n=0; while(isalpha(S[n]=gc())) ++n;//会有个换行符啊=_= 所以while的时候n先别加。
    	int m=0; while(isalpha(T[m]=gc())||T[m]=='?') ++m;
    
    	for(int i=0; i<n; ++i) pos[S[i]-97].set(i);
    	Ans.set();
    	for(int i=0; i<m; ++i) if(T[i]!='?') Ans&=pos[T[i]-97]>>i;
    
    	int ans=0;
    	for(int i=0; i<=n-m; ++i) if(Ans[i]==1) p[++ans]=i;
    	printf("%d
    ",ans);
    	for(int i=1; i<=ans; printf("%d
    ",p[i++]));
    
    	return 0;
    }
    
  • 相关阅读:
    7-1 N个数求和
    3662. 最大上升子序列和
    树状数组
    堆优化Dijkstra java模板
    皮亚诺曲线距离
    最长公共子序列(计数问题)
    最小路径覆盖
    极角排序
    2619. 询问
    Hessian矩阵与局部极小值
  • 原文地址:https://www.cnblogs.com/SovietPower/p/10041397.html
Copyright © 2011-2022 走看看