DP柿子满足卷积形式均可以用CDQ+NTT优化
,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
直接上 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的排列
由于前面有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;
}