题目:
题解:
首先这个n特别大,而且这个转移怎么也是二维的,所以矩乘也不像。
我们考虑拆一下这样一个【上升数】,11234=11111+111+11+1,每一个【上升数】被拆完之后不会超过9位,而且为了避免总共选0位有前导零,我们起初先填上11…111(共n位)。
这样会发现一个规律:1,11,111,1111…这些数字%p会有一个循环节(循环节不一定是从第一位开始的!)。那么我们设cnt[i]表示n位数字%p=i的数的个数,事实上要选择一些数使%p=0也就是选择cnt的下标使得%p=0的选法。
DP方程就可以写了:f[i][j][k]表示目前选到cnt[i],%p=j,选择了k个的方案数,其中i,j<=p,k<=9。这样的复杂度就是O(p^2*9*9),可以通过此题
f[i][(j+l∗i)%p][k+l]=∑f[i−1][j][k]∗C(cnt[i]+l−1,l) f [ i ] [ ( j + l ∗ i ) % p ] [ k + l ] = ∑ f [ i − 1 ] [ j ] [ k ] ∗ C ( c n t [ i ] + l − 1 , l )
其中在n个数里选m个,每个元素可以重复选的方案数是C(n+m-1,m),可以预处理
要注意,为了避免总共选0位有前导零,我们起初先填上11…111(共n位),这样DP方程k可以选择的范围是[0,8],而且最后的答案也不一定是f[last][0][j],而是除去了这n位1%p的值后,使总的%p=0的值
其实数组f[500][500][9]也可以开下,但是我们可以用滚动数组来优化。
代码:
#include <cstdio>
#include <cstring>
#define LL long long
using namespace std;
const int mod=999911659;
int f[2][505][10],hh[505],bj[505];
LL cnt[505],c[505][20],inv[20];
LL C(LL n,LL m)
{
LL ans=inv[m];
for (n%=mod;m--;n--) ans=ans*n%mod;
return ans;
}
int main()
{
int xhj=0,p,id=0,add=0;LL n;
scanf("%lld%d",&n,&p);
if (n<=p)
{
while (id<n)
{
id++;add=(add*10+1)%p;
cnt[add]++;
}
}
else
{
int len=0;
while (1)
{
add=(add*10+1)%p;
if (bj[add]) break;
cnt[add]++,bj[add]=++len,hh[len]=add;
}
xhj=len;len=len-bj[add]+1;
LL z=(n-xhj)/len,y=(n-xhj)%len;
for (int i=bj[add];i<bj[add]+len;i++) cnt[hh[i]]+=z;
for (int i=1;i<y;i++)
cnt[add]++,add=(add*10+1)%p;
if (y>=1) cnt[add]++;
}
add=(p-add)%p;
inv[0]=inv[1]=1;
for (int i=2;i<=9;i++) inv[i]=1LL*(mod-mod/i)*inv[mod%i]%mod;
for(int i=2;i<=9;i++) inv[i]=1LL*inv[i]*inv[i-1]%mod;
for (int i=0;i<p;i++) if (cnt[i])
for (int j=0;j<9;j++) c[i][j]=C(cnt[i]+j-1,j);
f[0][0][0]=1;
for (int i=1;i<9;i++) f[0][0][i]=c[0][i];
int now=0;
for (int i=1;i<p;i++)
if (cnt[i])
{
now^=1; memset(f[now],0,sizeof(f[now]));
for (int j=0;j<p;j++)
for (int k=0;k<9;k++) //当前
if (f[now^1][j][k])
for (int l=0;l<9-k;l++) //要加
f[now][(j+l*i)%p][k+l]=(f[now][(j+l*i)%p][k+l]+f[now^1][j][k]*c[i][l]%mod)%mod;
}
int ans=0;
for (int i=0;i<9;i++) ans=(ans+f[now][add][i])%mod;
printf("%d",ans);
}