把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【LOJ6059】「2017 山东一轮集训 Day1」Sum(倍增优化数位DP+NTT)

点此看题面

大致题意: 对于\(i=1\sim m\),分别求出有多少个\(n\)位数,满足它是\(p\)的倍数,且各位之和小于等于\(i\)

暴力数位\(DP\)

不看数据范围,这道题显然就是个数位\(DP\)

考虑设\(f_{i,j,k}\)表示考虑了\(i\)位,模\(p\)\(j\),且数位和为\(k\)的方案数,转移只要枚举填\(0\sim9\)即可,非常显然。

但是,\(n\le10^9\)。。。

对于这种数据范围,我们考虑倍增优化\(DP\)

倍增

考虑从\(f_n\)\(f_{n+1}\),和暴力\(DP\)一样,只要枚举填\(0\sim9\)即可。

然后考虑从\(f_n\)\(f_{2n}\),我们可以枚举两部分各自模\(p\)的余数\(x,y\),转移如下:

\[f_{2n,(x\times 10^n+y)\%p,u+v}=f_{n,x,u}\times f_{n,y,v} \]

这个式子最后一维明显是一个卷积形式,因此可以\(NTT\)优化。

于是这道题就做完了,具体实现详见代码。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define M 1000
#define X 998244353
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
int n,p,m,tn,f[50][M+5],tmp[50][M+5];
I int QP(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
namespace Poly//多项式
{
	#define PR 3
	#define IPR 332748118
	int P,L,Inv,A[M<<2],B[M<<2],R[M<<2];I void Init()//初始化,因为每次卷积数组长度相同
	{
		P=1,L=0;W(P<=(m<<1)) P<<=1,++L;Inv=QP(P,X-2);
		for(RI i=0;i^P;++i) R[i]=((R[i>>1]>>1)|((i&1)<<L-1));
	}
	I void NTT(int *s,CI p)//NTT
	{
		RI i,j,k,x,y,U,S;for(i=0;i^P;++i) i<R[i]&&(x=s[i],s[i]=s[R[i]],s[R[i]]=x);
		for(i=1;i^P;i<<=1) for(U=QP(p,X/(i<<1)),j=0;j^P;j+=i<<1) for(S=1,k=0;k^i;
			S=1LL*S*U%X,++k) s[j+k]=((x=s[j+k])+(y=1LL*S*s[i+j+k]%X))%X,s[i+j+k]=(x-y+X)%X;
	}
	I void Mul(int *a,int *b,int *res)//多项式乘法
	{
		RI i;memset(A,0,sizeof(int)*P),memset(B,0,sizeof(int)*P);
		for(i=0;i<=m;++i) A[i]=a[i],B[i]=b[i];
		for(NTT(A,PR),NTT(B,PR),i=0;i^P;++i) A[i]=1LL*A[i]*B[i]%X;
		for(NTT(A,IPR),i=0;i<=m;++i) res[i]=(1LL*A[i]*Inv+res[i])%X;
	}
}
I void Solve(CI x)//倍增优化DP,其实我写得类似于一个快速幂的过程
{
	if(!x) return (void)(f[0][0]=tn=1);Solve(x>>1);
	RI i,j,k;for(i=0;i^p;++i) for(j=0;j^p;++j) Poly::Mul(f[i],f[j],tmp[(tn*i+j)%p]);//n->2n
	for(i=0;i^p;++i) for(j=0;j<=m;++j) f[i][j]=tmp[i][j],tmp[i][j]=0;
	if(x&1)
	{
		for(i=0;i^p;++i) for(j=0;j<=m;++j)//n->n+1
			for(k=0;k<=9&&j+k<=m;++k) Inc(tmp[(10*i+k)%p][j+k],f[i][j]);//枚举填的数暴力DP
		for(i=0;i^p;++i) for(j=0;j<=m;++j) f[i][j]=tmp[i][j],tmp[i][j]=0;
	}
	tn=tn*tn%p,x&1&&(tn=tn*10%p);//维护10^n
}
int main()
{
	scanf("%d%d%d",&n,&p,&m),Poly::Init(),Solve(n);
	for(RI i=0,t=0;i<=m;++i) Inc(t,f[0][i]),printf("%d%c",t," \n"[i==m]);return 0;//注意做前缀和
}
posted @ 2020-07-25 13:47  TheLostWeak  阅读(170)  评论(0编辑  收藏  举报