统计 (dp 求n位无前导零且数码从高到低非降并模 m等于 0的数个数 )

统计

【问题描述】
求n位无前导零且数码从高到低非降并模 m等于 0的数个数。
【输入格式】
两行个整数 n, m;
【输出格式】
一行个整数代表答案模 10^9+7之后的值 。
【样例输入1】
2
12
【样例输出1】
4
【样例输入2】
3
111
【样例输出2】
9
【样例输入3】
452
10
【样例输出3】
0
【样例输入4】
6
58
【样例输出4】
38
【数据规模与约定】
对于 100%的数据, 1≤n≤10^18, 1≤m≤500,有各种部分 。

思路:
神奇的dp,%大佬,get代码,add注释

#include <cstdio>  
#include <cstring>  
#include <algorithm>
#include <iostream>
#define LL long long
using namespace std; 

const int M=520, P=1000000007;

LL n;
int m,lst[M],cnt[M],inv[10],f[9][M],C[M][9],g[M][9],u[9][9];
//考虑ans的性质,非下降,位数又是固定的,所以我们可以把ans分成最多九个形如11111..的数
//eg:12345 = 11111 + 1111 + 111 + 11 + 1; 155579 = 111111 + 11111 + 11111 + 11111 + 11111 + 11 + 11 + 1 + 1;
//因为ans的位数固定为n;所以最下面一层一定是n个1这个数。 
//那么我们就可以算这类型的数可以组成多少符合条件的数。 

int main(){
    freopen ("c.in", "r", stdin);
    freopen ("c.out", "w", stdout);
    inv[1] = 1;
    for(int i=2; i<=8; i++)//预处理1~8的逆元 
        inv[i] = (LL)inv[P%i] * (P-P/i)%P;//递推求法 
    /*for(int i=1; i<=8; i++)
        cout << inv[i] << endl;*/
    scanf("%I64d%I64d", &n, &m);
    //n = 15, m = 12;
    int v=0;
    for(int i=1; i<=n; i++){
        v = (v * 10 + 1) % m;//遍历所有位数在1~n之间,形似1,11,111的数字 
        if(v==0 || lst[v]){//之前枚举过一个数字 % m = v,也就是说我们找到了循环(v=0说明循环节就是1)  
            int sz=i-lst[v], tv=v;//sz就是循环节的长度 
            for(int j=0; j<sz; j++){//遍历整个循环节的模数 
                if((n-i-j) % sz == 0) v = tv;//剩下的数字个数是sz的整数倍,也就是说tv(v)就是最后一个数(n个1)%m的ans 
                cnt[tv] = (cnt[tv] + (n>=i+j ? (n-i-j+sz)/sz : 0)) % P;//在一个循环节内每个模数只会出现一次
                //统计之后有几个循环节就可以知道这一堆数中(1个1,2个1...)%m=x的有cnt[x]个 
                tv = (tv * 10 + 1) % m;
            }
            break;
        }
        cnt[v]++;
        lst[v] = i;//i个1 % m = v 
    }
    for(int i=0; i<m; i++){//
        int t=1;
        for(int j=0;j<9;j++){
            C[i][j] = t;//C[i][j]表示从这一堆数(1个1,2个1...)中找出j个%m=i的数有多少种方案 
            t = (LL)t * (cnt[i]-j) % P * inv[j+1] % P;//阶乘式子往下走 
            //printf("Y %d %d %d\n", i, j, [i][j]);
        }
    }
    u[0][0] = 1;//u[i][j]表示在i层中填充j种数的方案数,注意是填充而不是选j层出来,每个数一定要放进去。
    //eg:u[3][2] = 2; 也就是说在3层中填充2种数的方案数为2,假设我们的2种数是1,2
    //填充方式是1,1,2或者1,2,2。不计顺序(因为我们最终的ans是它们的和) 
    //u[i][j] = C(i-1, j-1)(组合数) 夹棍法考虑,因为不计顺序,那么我们的任务就是把i层分成j段,每段一个数
    //所以u的求法就有两种下面是根据意义的递推,也可以直接算组合数 
    for(int i=1; i<9; i++)
        for(int j=1; j<=i; j++){
            for(int k=1; k<=i; k++)
                (u[i][j] += u[i-k][j-1]) %= P;//递推
                //在i层中填充j种数的方案数=在i-k层中填充j-1种数的方案数(剩下的层填充最后一种)顺序并没有影响
            //printf("Y %d %d %d\n", i, j, [i][j]); 
        }
    for(int i=0; i<m; i++){
        for(int j=0; j<9; j++){
            for(int k=0; k<=j; k++)
                g[i][j] = (g[i][j] + (LL)u[j][k] * C[i][k]) % P;
                //g[i][j]表示从这一堆数(1个1,2个1...)中找出j个%m=i的数的方案数(一个数可以选择多次)
                //从这一堆数(1个1,2个1...)中找出个%m=i的数的方案数 有C[i][k]
                //把这k个数放到这j层中的方案数有u[j][k]种,所以就是这样递推 
            //printf("Y %d %d %d\n", i, j, g[i][j]);
        }
    }
    f[0][v] = 1;//f[i][j]表示放上i层,使得所得ans%m=j的方案数 
    for(int i=0; i<m; i++){
        for(int j=7; j>=0; j--){//类似背包 
            for(int k=j+1; k<9; k++){
                for(int l=0; l<m; l++){
                    int &t = f[k][(l+i*(k-j))%m];
                    t = (t + (LL)f[j][l] * g[i][k-j]) % P;//选择k-j层放上%m=i的数,ans%m就=l+i*(k-j) 
                }
            }
        }
    }
    int ans = 0;
    for(int i=0; i<9; i++)
        (ans += f[i][0]) %= P;
    printf("%d\n", ans);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值