NTT(FFT)+CDQ优化DP

DP柿子满足卷积形式均可以用CDQ+NTT优化

\small dp[i]=\sum_{k=1}^{i}dp[i-k]*f1[k]/f2[i-k] =\sum ai*bi,ai,bi,为两个生成函数的系数

 

例题1.hdu5322  模数资瓷NTT,ans=dp[n],n<=1e5(下面柿子的(j-1)!应该是(i-1)! )

系数ai=dp[i]/i! ,bi=i*i,在CDQ里用NTT

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<bits/stdc++.h>
#define ll long long
using namespace std;
//AC hdu5322 998ms
inline int gi(){//快读 
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
    return f?x:-x;
}

const int _ = 4e5+5;//2倍 
const int mod = 998244353;//模数 
ll n,m,s,N,lim,ans;ll jc[_];
ll inv[_],a[_],b[_],rev[_],l,og[_];//NTT本身用到的数组 
ll qmod(ll a,ll b){//快速幂 
    int res=1;
    while (b) {if (b&1) res=1ll*res*a%mod;a=1ll*a*a%mod;b>>=1;}
    return res;
}
void ntt(ll *P,ll len,int opt){//基本上不用改 
    for (int i=0;i<len;++i) if (i<rev[i]) swap(P[i],P[rev[i]]);
    for (int i=1;i<len;i<<=1){
        ll W=qmod(3,(mod-1)/(i<<1));//3 根据模数的原根  可能要改 
        if (opt==-1) W=qmod(W,mod-2);
        og[0]=1;
        for (int j=1;j<i;++j) og[j]=1ll*og[j-1]*W%mod;
        for (int p=i<<1,j=0;j<len;j+=p)
            for (int k=0;k<i;++k){
                ll x=P[j+k],y=1ll*og[k]*P[j+k+i]%mod;
                P[j+k]=(x+y)%mod,P[j+k+i]=(x-y+mod)%mod;
            }
    }
    if (opt==-1) for (int i=0,Inv=qmod(len,mod-2);i<len;++i) P[i]=1ll*P[i]*Inv%mod;
}

ll dp[100005];
void cdq(int l,int r)
{
	if(l==r)return ;
	int m=l+r>>1;
	cdq(l,m);
	ll len,lll=0;for (len=1;len<=((r-l+1)<<1);len<<=1) ++lll;--lll;//固定 
	for (int i=0;i<len;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<lll);//固定 
	
	//预处理系数 
	for(int i=0;i<len;i++){
		a[i]=b[i]=0;
		if(i<=r-l+1) b[i]=1ll*i*i%mod;//预处理bi 不固定  非dp项1~r-l+1 
	}
	for(int i=l;i<=m;i++) a[i-l+1]=1ll*dp[i]*inv[i]%mod;//预处理ai 不固定 dp项 l~m
	
	ntt(a,len,1);ntt(b,len,1); 
    for (int i=0;i<len;++i) a[i]=1ll*a[i]*b[i]%mod;
    ntt(a,len,-1);
    
    for(int i=m+1;i<=r;i++) dp[i]=(dp[i]+1ll*a[i-l+1]*jc[i-1]%mod)%mod;//将[l~m]的影响加到dp[m+1~r]上 
    cdq(m+1,r);
}


int main(){
    //n=gi();m=gi();//快读
	//N=max(n,m);//求和表达式 上限   (3.会用到) 
	lim=_-3;//阶乘上限   (非NTT)
	
	//预处理阶乘 跟逆元阶乘 (非NTT)
    jc[0]=1; for (int i=1;i<=lim;++i) jc[i]=1ll*jc[i-1]*i%mod;
    inv[lim]=qmod(jc[lim],mod-2);
    for (int i=lim;i;--i) inv[i-1]=1ll*inv[i]*i%mod;
    
    memset(dp,0,sizeof(dp));
	dp[0]=1;
    cdq(0,100000);//预处理DP 
    while(~scanf("%d",&n))printf("%d\n",dp[n]%mod);
    
    
    //1.NTT   固定操作,处理数据的两倍 对2^k向上取整=len 
    //for (len=1;len<=(N<<1);len<<=1) ++l;--l;
    
    //2.NTT	  固定操作,预处理rev 蝶形变换 
    //for (int i=0;i<len;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<l);
    
    //3.NTT   灵活操作  ai bi的表达式 
    //for (int i=0;i<=n;++i) a[i]=gi();
    //for (int i=0;i<=m;++i) b[i]=gi();
    
    //4.NTT   固定操作  ai卷积bi  也就是(a0+a1x+a2x^2...)*(b0+b1x+b2x^2....)=sum_{i=0...n}sum_{j=0...i}ai*b(j-i)
    //ntt(a,1);ntt(b,1); 
    //for (int i=0;i<len;++i) a[i]=1ll*a[i]*b[i]%mod;
    //ntt(a,-1);
    
    //5.NTT非固定操作  计算求和表达式的答案ans  
    //for (int i=0;i<=n+m;++i) printf("%d ",a[i]); puts("");
    //for(int i=0;i<=m;i++) printf("%d ",a[i]);
    //printf("%d\n",ans);
    
    
    return 0;
}

 

例题2.hdu5730   ans=dp[n],n<=1e5

ai=dp[i] bi=a[i],a[i]是题目给的= =

#include<bits/stdc++.h>
using namespace std;
#define ll long long 
const double eps=1e-8;

const double PI = acos(-1.0);//精度不够会WA换long double 

struct Complex{
    double real, image;//精度不够会WA换long double 
    Complex(double _real, double _image)//精度不够会WA换long double 
    {
        real = _real;
        image = _image;
    }
    Complex() {}
};
Complex operator + (const Complex &c1, const Complex &c2){
    return Complex(c1.real + c2.real, c1.image + c2.image);
}
Complex operator - (const Complex &c1, const Complex &c2){
    return Complex(c1.real - c2.real, c1.image - c2.image);
}
Complex operator * (const Complex &c1, const Complex &c2){
    return Complex(c1.real*c2.real - c1.image*c2.image, c1.real*c2.image + c1.image*c2.real);
}
int rev(int id, int len)
{
    int ret = 0;
    for(int i = 0; (1 << i) < len; i++)
    {
        ret <<= 1;
        if(id & (1 << i)) ret |= 1;
    }
    return ret;
}
Complex A[1 << 19];//tmp
void FFT(Complex *a, int len, int DFT)
{
    for(int i = 0; i < len; i++)
        A[rev(i, len)] = a[i];
    for(int s = 1; (1 << s) <= len; s++)
    {
        int m = (1 << s);
        Complex wm = Complex(cos(DFT*2*PI/m), sin(DFT*2*PI/m));
        for(int k = 0; k < len; k += m)
        {
            Complex w = Complex(1, 0);
            for(int j = 0; j < (m >> 1); j++)
            {
                Complex t = w*A[k + j + (m >> 1)];
                Complex u = A[k + j];
                A[k + j] = u + t;
                A[k + j + (m >> 1)] = u - t;
                w = w*wm;
            }
        }
    }
    if(DFT == -1) for(int i = 0; i < len; i++) A[i].real /= len, A[i].image /= len;
    for(int i = 0; i < len; i++) a[i] = A[i];
    return;
}
Complex a[1<<19];//系数数组,后面要移到复数数组,好像直接开复数数组比较好, 
Complex b[1<<19];
int dp[100005];int vv[100005];
void cdq(int l,int r)
{
	if(l==r) return ;
	int m=l+r>>1;
	cdq(l,m);
	int len=1;while(len<=r-l+1)len=len<<1;len=len<<1;
	
	//预处理系数 
	for(int i=0;i<len;i++)a[i]=b[i]=Complex(0,0);
	for(int i=l;i<=m;i++)b[i-l+1]=Complex(dp[i],0);//dp 只能l~m 
	for(int i=1;i<=r-l+1;i++)a[i]=Complex(vv[i],0);//非dp项需要l~r 
	
	FFT(a,len,1); FFT(b,len,1);
	for(int i=0;i<len;i++)a[i]=a[i]*b[i];
	FFT(a,len,-1);
	
	for(int i=m+1;i<=r;i++)dp[i]=dp[i]+(ll)(a[i-l+1].real+0.5),dp[i]%=313;
	cdq(m+1,r);
}
int main()
{
   int n;
   while(~scanf("%d",&n))
   {
   		if(n==0)break;
   		memset(vv,0,sizeof(vv));
   		for(int i=1;i<=n;i++)scanf("%d",&vv[i]),vv[i]%=313;
   		memset(dp,0,sizeof(dp));
   		dp[0]=1;
   		cdq(0,n);
   		printf("%d\n",dp[n]%313);
   }
   
}

 

例题3.BZOJ4555   n<=1e5

\small DP[n]=n!\sum_{i=1}^{n}\frac{2*DP[n-i]}{i!*(n-i)!},ans=\sum_{i=0}^{n}DP[i]

直接上 bi=2/i!,ai=dpi/i!

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<bits/stdc++.h>
#define ll long long
using namespace std;
//AC hdu5322 998ms
//AC BZOJ4555 26111ms
//dp[i]=i!*sum dp[i-j]*2/j!;
inline int gi(){//快读 
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
    return f?x:-x;
}

const int _ = 4e5+5;//2倍 
const int mod = 998244353;//模数 
int n,m,s,N,lim,ans;int jc[_];
int inv[_],a[_],b[_],rev[_],l,og[_];//NTT本身用到的数组 
int qmod(int a,int b){//快速幂 
    int res=1;
    while (b) {if (b&1) res=1ll*res*a%mod;a=1ll*a*a%mod;b>>=1;}
    return res;
}
void ntt(int *P,int len,int opt){//基本上不用改 
    for (int i=0;i<len;++i) if (i<rev[i]) swap(P[i],P[rev[i]]);
    for (int i=1;i<len;i<<=1){
        ll W=qmod(3,(mod-1)/(i<<1));//3 根据模数的原根  可能要改 
        if (opt==-1) W=qmod(W,mod-2);
        og[0]=1;
        for (int j=1;j<i;++j) og[j]=1ll*og[j-1]*W%mod;
        for (int p=i<<1,j=0;j<len;j+=p)
            for (int k=0;k<i;++k){
                ll x=P[j+k],y=1ll*og[k]*P[j+k+i]%mod;
                P[j+k]=(x+y)%mod,P[j+k+i]=(x-y+mod)%mod;
            }
    }
    if (opt==-1) for (int i=0,Inv=qmod(len,mod-2);i<len;++i) P[i]=1ll*P[i]*Inv%mod;
}

ll dp[100005];
void cdq(int l,int r)
{
	if(l==r)return ;
	int m=l+r>>1;
	cdq(l,m);
	ll len,lll=0;for (len=1;len<=((r-l+1)<<1);len<<=1) ++lll;--lll;//固定 
	for (int i=0;i<len;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<lll);//固定 
	//预处理系数 
	for(int i=0;i<len;i++){
		a[i]=b[i]=0;
		if(i<=r-l+1) b[i]=1ll*2*inv[i]%mod;//预处理bi 不固定  非dp项1~r-l+1 
	}
	for(int i=l;i<=m;i++) a[i-l+1]=1ll*dp[i]*inv[i]%mod;//预处理ai 不固定 dp项 l~m
	ntt(a,len,1);ntt(b,len,1); 
    for (int i=0;i<len;++i) a[i]=1ll*a[i]*b[i]%mod;
    ntt(a,len,-1);
    for(int i=m+1;i<=r;i++) (dp[i]+=1ll*a[i-l+1]*jc[i]%mod)%=mod;//将[l~m]的影响加到dp[m+1~r]上 
    cdq(m+1,r);
}


int main(){
    //n=gi();m=gi();//快读
	//N=max(n,m);//求和表达式 上限   (3.会用到) 
	lim=_-3;//阶乘上限   (非NTT)
	
	//预处理阶乘 跟逆元阶乘 (非NTT)
    jc[0]=1; for (int i=1;i<=lim;++i) jc[i]=1ll*jc[i-1]*i%mod;
    inv[lim]=qmod(jc[lim],mod-2);
    for (int i=lim;i>=1;--i) inv[i-1]=1ll*inv[i]*i%mod;inv[0]=1;
    
    //memset(dp,0,sizeof(dp));
	dp[0]=1;
	scanf("%d",&n);
    cdq(0,n);//预处理DP 
    
	int ans=0;
	for(int i=0;i<=n;i++)(ans+=1ll*dp[i])%=mod;
	printf("%d\n",ans%mod);
    
    
    //1.NTT   固定操作,处理数据的两倍 对2^k向上取整=len 
    //for (len=1;len<=(N<<1);len<<=1) ++l;--l;
    
    //2.NTT	  固定操作,预处理rev 蝶形变换 
    //for (int i=0;i<len;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<l);
    
    //3.NTT   灵活操作  ai bi的表达式 
    //for (int i=0;i<=n;++i) a[i]=gi();
    //for (int i=0;i<=m;++i) b[i]=gi();
    
    //4.NTT   固定操作  ai卷积bi  也就是(a0+a1x+a2x^2...)*(b0+b1x+b2x^2....)=sum_{i=0...n}sum_{j=0...i}ai*b(j-i)
    //ntt(a,1);ntt(b,1); 
    //for (int i=0;i<len;++i) a[i]=1ll*a[i]*b[i]%mod;
    //ntt(a,-1);
    
    //5.NTT非固定操作  计算求和表达式的答案ans  
    //for (int i=0;i<=n+m;++i) printf("%d ",a[i]); puts("");
    //for(int i=0;i<=m;i++) printf("%d ",a[i]);
    //printf("%d\n",ans);
    
    
    return 0;
}

 

例题4:51nod1514  NTT+DP

题意:dp[n]表示:满足任意1<=i<=n-1,下标1~i都是1~i的排列的1~n的排列

\large dp[n]=n!-\sum_{i=0}^{n-1}dp[i]*(n-i)!

由于前面有n!,所以CDQ里l==r加上n!对DP的贡献

#include<bits/stdc++.h>
#define ll long long
using namespace std;
//NTT+CDQ
//AC hdu5322 998ms
//AC BZOJ4555 26111ms
//dp[i]=i!*sum dp[i-j]*2/j!;
inline int gi(){//快读 
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
    return f?x:-x;
}

const int _ = 4e5+5;//2倍 
const int mod = 998244353;//模数 
int n,m,s,N,lim,ans;int jc[_];
int inv[_],a[_],b[_],rev[_],l,og[_];//NTT本身用到的数组 
int qmod(int a,int b){//快速幂 
    int res=1;
    while (b) {if (b&1) res=1ll*res*a%mod;a=1ll*a*a%mod;b>>=1;}
    return res;
}
void ntt(int *P,int len,int opt){//基本上不用改 
    for (int i=0;i<len;++i) if (i<rev[i]) swap(P[i],P[rev[i]]);
    for (int i=1;i<len;i<<=1){
        ll W=qmod(3,(mod-1)/(i<<1));//3 根据模数的原根  可能要改 
        if (opt==-1) W=qmod(W,mod-2);
        og[0]=1;
        for (int j=1;j<i;++j) og[j]=1ll*og[j-1]*W%mod;
        for (int p=i<<1,j=0;j<len;j+=p)
            for (int k=0;k<i;++k){
                ll x=P[j+k],y=1ll*og[k]*P[j+k+i]%mod;
                P[j+k]=(x+y)%mod,P[j+k+i]=(x-y+mod)%mod;
            }
    }
    if (opt==-1) for (int i=0,Inv=qmod(len,mod-2);i<len;++i) P[i]=1ll*P[i]*Inv%mod;
}

ll dp[100005];
void cdq(int l,int r)
{
	if(l==r)return (void)(dp[l]=(jc[l]-dp[l]+mod)%mod);
	int m=l+r>>1;
	cdq(l,m);
	ll len,lll=0;for (len=1;len<=((r-l+1)<<1);len<<=1) ++lll;--lll;//固定 
	for (int i=0;i<len;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<lll);//固定 
	
	//预处理系数 
	for(int i=0;i<len;i++){
		a[i]=b[i]=0;
		if(i<=r-l+1) b[i]=1ll*jc[i]%mod;//预处理bi 不固定  非dp项1~r-l+1 
	}
	for(int i=l;i<=m;i++) a[i-l+1]=1ll*dp[i]%mod;//预处理ai 不固定 dp项 l~m
	
	ntt(a,len,1);ntt(b,len,1); 
    for (int i=0;i<len;++i) a[i]=1ll*a[i]*b[i]%mod;
    ntt(a,len,-1);
    
    for(int i=m+1;i<=r;i++) dp[i]=((dp[i]+a[i-l+1])%mod)%mod;//将[l~m]的影响加到dp[m+1~r]上 
    cdq(m+1,r);
}


int main(){
    //n=gi();m=gi();//快读
    //N=max(n,m);//求和表达式 上限   (3.会用到) 
    lim=_-3;//阶乘上限   (非NTT)
    
    //预处理阶乘 跟逆元阶乘 (非NTT)
    jc[0]=1; for (int i=1;i<=lim;++i) jc[i]=1ll*jc[i-1]*i%mod;
    
    //memset(dp,0,sizeof(dp));
    dp[0]=1;
    cdq(0,100000);//预处理DP 
	int T;scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);printf("%d\n",dp[n]);
	}
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值