【BZOJ1974】[SDOI2010] 代码拍卖会(动态规划)
大致题意: 让你生成一个\(n\)位数,满足从高到低每一位不降,求这个数是\(p\)的倍数的方案数。
前言
终于明白,现在的我与过去相比,唯一进步的,就是能在错误的道路上走得更远。
很奇怪,每次做题总是搞错方向,却又偏偏能推出很多性质来,搞得非常正确一样。
虽然有了深刻自我认识之后我经常试着换一种思路,结果从未成功过。
菜是原罪,或许菜已经融化于我的血肉中,铭刻在我的骨髓里,烙印在我的灵魂上,使我永远都只能是这么菜。
转化
考虑如果我们把右起第\(i\)位作为一个关键点(即改变了数的大小),则就相当于是给这个数加上了\(11...1\)(\(i\)个\(1\))。当然如果这个数比前一位大了不止\(1\),就相当于加上多个\(11...1\)。
于是题意就转化成了,求至多九个完全由\(1\)组成的数,它们的和是\(p\)的倍数的方案数。
归类
由于最后要求是\(p\)的倍数,容易发现模\(p\)同余的数其实对余数的影响是一样的。
换言之,我们只要统计出\(tot_x\)表示模\(p\)余\(x\)的数的个数,就根本无需考虑我们选择的具体是什么数。
然后考虑如何求\(tot_x\)。显然,我们暴力枚举\(1,11,111,...,11...1\),当某一刻两个数模\(p\)同余,就意味出现了循环。而这个计算应该是比较简单的吧,可见代码。
动态规划
好,现在我们求出了\(tot_x\),然后就可以开心地动态规划了。
我们设\(f_{i,j,k}\)表示处理过余数为\(0\sim i-1\)的数(这个定义比较鬼畜,主要是为了方便刷表法),共选择了\(j\)个数,当前余数为\(k\)的方案数。
则只要枚举一个\(x\)表示选择\(x\)个余数为\(i\)的数,就可以转移至\(f_{i+1,j+x,(k+i\times x)\ mod\ p}\)。
注意这里的转移系数,由于是在\(tot_i\)种数中选\(x\)个,我们添上\(x-1\)个数,其中第\(a\)个表示第\(a+1\)个数是否和第\(a\)个数选的是同一个数,因此就是\(C_{tot_i+x-1}^{x}\)。显然这个东西可以在枚举\(x\)的同时一并维护。
还有注意初始条件,由于第\(n\)位是必须选的,所以我们初始化\(f_{0,1,t}=1\)(其中\(t\)表示\(n\)个\(1\)模\(p\)的余数)。
具体实现详见代码。
代码
#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 P 500
#define LL long long
#define X 999911659
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
LL n;int p,s[P+5],vis[P+5],tot[P+5],f[P+5][10][P+5];
const int Inv[10]={1,1,499955830,666607773,249977915,199982332,833259716,571378091,624944787,222202591};//懒得写预处理,直接手算打表
int main()
{
RI i,j,k,x,t;scanf("%lld%d",&n,&p),x=1%p,t=0;W(!vis[x]) s[vis[x]=++t]=x,x=(10*x+1)%p;//找循环
for(i=0;i^p;++i) vis[i]&&vis[i]<=n&&(tot[i]=vis[i]<vis[x]?1:((n-(vis[i]-1)-1)/(t-vis[x]+1)+1)%X);//计算每个数出现次数
f[0][1][s[vis[x]+(n-vis[x])%(t-vis[x]+1)]]=1;//初始条件
RI C;for(i=0;i^p;++i) for(j=1;j<=9;++j) for(k=0;k^p;++k)//枚举i,j,k刷表
{
C=1,Inc(f[i+1][j][k],f[i][j][k]);//初始化组合数,并直接转移不选的情况
for(x=1;x+j<=9;++x) C=1LL*C*(tot[i]+x-1)%X*Inv[x]%X,Inc(f[i+1][x+j][(k+i*x)%p],1LL*C*f[i][j][k]%X);//维护组合数,DP转移
}
RI ans=0;for(i=1;i<=9;++i) Inc(ans,f[p][i][0]);return printf("%d",ans),0;//统计答案输出
}