zoukankan      html  css  js  c++  java
  • 关于分治FFT"我卷我自己"形式

    以下只讨论这种一般的"我卷我自己"形式:

    [f_n=sumlimits_{i=1}^{n-1}f_if_{n-i} ]


    如果式子是(f_n=sumlimits_{i=1}^{n-1}f_ig_{n-i})的话,就是普通的分治FFT了。

    普通分治FFT做法是:solve(l,r)时,先行递归计算左半区间solve(l,mid),得到(f_{l,,,mid})的准确值之后,卷上(g_{1,,,r-l}),这样把[l,mid]的权值贡献给右半区间[mid+1,r],然后递归计算右半区间solve(mid+1,r)。

    我们把这个贡献方式记为:[l,mid]*[1,r-l]贡献给右区间。

    在这里,(g_{n-i})都是预先给出的。但是如果是我卷我自己形式的话,(f_{n-i})也是要求在计算过程中求出的。

    那么就有可能会碰到一个问题,当递归完左半区间打算计算其对右半区间的贡献时,我们可能并没有完全得到(f_{1,,,r-l})的准确值。

    这里就需要根据(l)的取值分成两种情况讨论:

    • (l eq 1)

      此时的(l)一定是形如([(mid=(1+r)/2)+1,r])的区间,即([1,r])的右半区间,或者从这类区间递归而来。递归过程中要么(l)增大,要么(r)减小,所以一定会有(lge r-l)成立。

      这意味着solve(l,mid)之后(f_{l,,,mid})(f_{1,,,r-l})的准确值都是已经求好了的。

      那么这种情况下贡献直接照普通分治FFT的方式计算即可。

      这里还有一个性质是[l,mid]和[1,r-l]无重叠部分,端点忽略不计(???)

    • (l=1)

      这时(1ge r-1)是不一定成立的。

      (f_{1,,,r-1})拆成两部分看:

      1. (f_{1,,,mid})部分。这一部分是在solve(1,mid)的时候已经算出来了的,所以同样可以直接按照普通分治FFT做法,(f_{1,mid})(f_{1,mid})卷一下贡献给右半区间。

      2. (f_{mid+1,,,r-1})部分(这里就是与普通FFT不同之处)。因为刚递归计算完[1,mid],所以这一部分当然是还没计算出准确值的,值都是0。此时[1,mid]*[mid+1,r-1]这部分就无法贡献给右半区间。

      但是注意到[mid+1,r-1]区间,其左端点是不等于1的。那么在计算完[mid+1,r-1]的准确值时,我们肯定已经知道[1,mid]的准确值,所以可以在那时再把贡献算上。

      即本来是要在solve(1,mid)时[1,mid]*[mid+1,r-1]贡献给右半区间,现在变成solve(mid+1,r-1)时[1,mid]*[mid+1,r-1]贡献给右区间。这里[1,mid]*[mid+1,r-1]恰好就是solve(mid+1,r-1)时本来要计算的(case1中)[mid+1,r-1]*[1,mid] swap一下的样子。

    总结一下,具体流程就是:

    首先递归solve(l,mid)计算完[l,mid]的准确值之后,

    (l=1),则只做[1,mid]*[1,mid]的贡献。

    (l eq 1),先做[l,mid]*[1,r-l]的贡献,然后再做[1,r-l]*[l,mid]的贡献以补偿在(l=1)时的未计算量。

    最后递归solve(mid+1,r)。

    一道例题:

    LOJ2554 「CTSC2018」青蕈领主

    Link

    Code

    #include<bits/stdc++.h>
    using namespace std;
    #define REP(i,a,b) for(int i=(a),_ed=(b);i<=_ed;++i)
    #define DREP(i,a,b) for(int i=(a),_ed=(b);i>=_ed;--i)
    #define mp(x,y) make_pair((x),(y))
    #define sz(x) (int)(x).size()
    #define pb push_back
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int,int> pii;
    inline int read(){
        register int x=0,f=1;register char ch=getchar();
        while(!isdigit(ch)){if(ch=='-')f=0;ch=getchar();}
        while(isdigit(ch)){x=x*10+(ch^'0');ch=getchar();}
        return f?x:-x;
    }
    
    const int P=998244353;
    int n,L[50005],f[140005];
    inline int power(int b,int n){int ans=1;for(;n;n>>=1,b=1ll*b*b%P)if(n&1)ans=1ll*ans*b%P;return ans;}
    inline void inc(int& x,int y){x=x+y<P?x+y:x+y-P;}
    inline void dec(int& x,int y){x=x-y>=0?x-y:x-y+P;}
    
    int trans[140005],w[140005];
    void NTT(int* a,int n){
    	static ull f[140005];
    	REP(i,0,n-1)f[i]=a[i];
    	REP(i,0,n-1)if(i<trans[i])swap(f[i],f[trans[i]]);
    	for(int len=2,d=1;len<=n;d=len,len<<=1)
    		for(int p=0;p<n;p+=len)
    			for(int i=p;i<p+d;++i){
    				int t=f[i+d]*w[d+i-p]%P;
    				f[i+d]=f[i]+P-t,f[i]+=t;
    			}
    	REP(i,0,n-1)a[i]=f[i]%P;
    }
    void times(int* f,int* a,int m1,int m2,int lim){
    	static int g[140005];
    	int n=1;for(;n<m1+m2-1;n<<=1);
    	REP(i,0,n-1)trans[i]=(trans[i>>1]>>1)|(i&1?(n>>1):0);
    	for(int len=2,d=1;len<=n;d=len,len<<=1){
    		int e=power(3,(P-1)/len);
    		REP(i,w[d]=1,d-1)w[d+i]=1ll*w[d+i-1]*e%P;
    	}
    	REP(i,m1,n-1)f[i]=0;REP(i,0,m2-1)g[i]=a[i];REP(i,m2,n-1)g[i]=0;
    	NTT(f,n),NTT(g,n);
    	REP(i,0,n-1)f[i]=1ll*f[i]*g[i]%P;
    	NTT(f,n);int inv=power(n,P-2);
    	reverse(f+1,f+n);
    	REP(i,0,lim-1)f[i]=1ll*f[i]*inv%P;
    	REP(i,lim,n-1)f[i]=0;
    }
    
    void solve(int l,int r){
    	static int A[140005],B[140005];
    	if(l==r)return inc(f[l],1ll*(l-1)*f[l-1]%P);
    	int mid=(l+r)>>1;
    	solve(l,mid);
    	if(l==1){
    		A[0]=A[1]=B[0]=B[1]=0;
    		REP(i,2,mid)A[i]=1ll*f[i]*(i-1)%P,B[i]=f[i];
    		times(A,B,mid+1,mid+1,r+1);
    		REP(i,mid+1,r)inc(f[i],A[i]);
    	}
    	else{
    		REP(i,l,mid)A[i-l]=1ll*(i-1)*f[i]%P;
    		B[0]=B[1]=0;
    		REP(i,2,r-l)B[i]=f[i];
    		times(A,B,mid-l+1,r-l+1,r-l+1);
    		REP(i,mid+1,r)inc(f[i],A[i-l]);
    		A[0]=A[1]=0;
    		REP(i,2,r-l)A[i]=1ll*(i-1)*f[i]%P;
    		REP(i,l,mid)B[i-l]=f[i];
    		times(A,B,r-l+1,mid-l+1,r-l+1);
    		REP(i,mid+1,r)inc(f[i],A[i-l]);
    	}
    	solve(mid+1,r);
    }
    
    int cal(int l,int r){
    	if(L[r]!=r-l+1)return 0;
    	int p=r-1,res=1,num=1;
    	while(p>=l)res=1ll*res*cal(p-L[p]+1,p)%P,p-=L[p],++num;
    	res=1ll*res*f[num-1]%P;
    	if(p<l-1)res=0;
    	return res;
    }
    
    int main(){
    	// freopen("in.in","r",stdin);
    	int T=read();n=read();
    	f[0]=1,f[1]=2;
    	solve(1,n-1);
    	REP(t,1,T){
    		REP(i,1,n)L[i]=read();
    		printf("%d
    ",cal(1,n));
    	}
    	return 0;
    }
    
    
  • 相关阅读:
    矩阵按键转化为普通单个按键
    表达式位长 对结果的影响
    LuoguP3674 小清新人渣的本愿 && BZOJ4810: [Ynoi2017]由乃的玉米田
    BZOJ2956: 模积和
    NOIP2016 天天爱跑步
    LuoguP3948 数据结构
    AT2442 フェーン現象 (Foehn Phenomena)
    博客园美化笔记
    BZOJ2242: [SDOI2011]计算器
    分块入门与分块的经典应用
  • 原文地址:https://www.cnblogs.com/fruitea/p/13034326.html
Copyright © 2011-2022 走看看