• [ZJOI2010] 数字计数


    类型:数位DP

    传送门:>Here<

    题意:给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次

    解题思路

    本题基本思路和上题很相似,只是前导零的问题比较麻烦啊……

    定义状态:$dp[i][j][k]$表示$i$位数,开头为$j$,数码$k$的出现次数。很容易想到转移方程$$dp[i][j][k] = sumlimits_{0 leq p leq 9} dp[i-1][p][k]$$并且特判$j=k$的情况,此时应当额外加上$10^{i-1}$

    如果还是按照正常的分层法去统计就会出现一些问题:例如,数字$23$可以存在于$dp[2][2][...]$,也可以存在于$dp[3][0][...]$。如果正常统计将为产生大量的重复——导致0的数量太大,这就是刚才所说的前导零带来的麻烦。而造成这种麻烦的却只在第一位,后面的位数又没有任何影响了。因此我们先抛开第一位统计其他所有数,也就是统计所有$0 ightarrow len-1$位的数。并且注意在统计这些数时,只能取$dp[i][1 ightarrow 9][...]$而不能取0。因为想象一下在上一题中我们取0的意义——当前这一位取0意味着位数减少了1. 而目前我们已经在枚举位数了,因此0作为开头的存在完全没有意义。

    然后我们强制第一位不能取0,并且开始做$len$位数的统计。但是此时我们又需要取到0了,因为这个时候取0是有意义的。并且统计位数的时候,我们不能忽略限制的那一位。我们正常的时候会扫$0 ightarrow digit[i]-1$,那么$digit[i]$作为答案什么时候累积呢?因此我们每次进入下一位(也就是确定digit[i])的时候进行累积:要加上余下的那么多次——$x \% 10^{i-1} + 1$

    Code

    和上一题不同,这里统计的就不是小于N的了,而是能够包括N。

    /*By DennyQi 2018.8.13*/
    #include <cstdio>
    #include <queue>
    #include <cstring>
    #include <algorithm>
    #define  r  read()
    #define  Max(a,b)  (((a)>(b)) ? (a) : (b))
    #define  Min(a,b)  (((a)<(b)) ? (a) : (b))
    using namespace std;
    typedef long long ll;
    #define int long long
    const int MAXN = 10010;
    const int MAXM = 27010;
    const int INF = 1061109567;
    inline int read(){
        int x = 0; int w = 1; register int c = getchar();
        while(c ^ '-' && (c < '0' || c > '9')) c = getchar();
        if(c == '-') w = -1, c = getchar();
        while(c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar(); return x * w;
    }
    int N,M,num;
    int digit[16],dp[16][14][14],ans1[12],ans2[12],pw[16];
    inline void Init(){
        pw[0] = 1;
        for(int i = 1; i <= 12; ++i){
            pw[i] = pw[i-1] * 10;
        }
        for(int j = 0; j <= 9; ++j){
            dp[1][j][j] = 1;
        }
        for(int i = 2; i <= 12; ++i){
            for(int j = 0; j <= 9; ++j){
                for(int k = 0; k <= 9; ++k){
                    if(j == k){
                        dp[i][j][k] += pw[i-1];
                    }
                    for(int p = 0; p <= 9; ++p){
                        dp[i][j][k] += dp[i-1][p][k];
                    } 
                }
            }
        }
    }
    inline void cul(int x){
        int y = x;
        num = 0;
        while(y > 0){
            digit[++num] = y % 10;
            y /= 10;
        }
        for(int i = 1; i < num; ++i){
            for(int j = 1; j <= 9; ++j){
                for(int k = 0; k <= 9; ++k){
                    ans1[k] += dp[i][j][k];
                }
            }
        }
        for(int i = num; i; --i){
            for(int j = 0; j < digit[i]; ++j){
                if(i == num && j == 0) continue;
                for(int k = 0; k <= 9; ++k){
                    ans1[k] += dp[i][j][k];
                }
            }
            ans1[digit[i]] += (x%pw[i-1] + 1);
        }
    }
    #undef int
    int main(){
    #define int long long
        N = r, M = r;
        Init();
        cul(M);
        for(int i = 0; i <= 9; ++i) ans2[i] = ans1[i];
        memset(ans1,0,sizeof(ans1));
        cul(N-1);
        for(int i = 0; i <= 9; ++i) printf("%lld ", ans2[i]-ans1[i]);
        return 0;
    }
  • 相关阅读:
    03 Linux下运行Django项目
    02 Linux常用基本命令(二)
    01 Linux常用基本命令(一)
    08 基本数据结构
    07 Deque的应用案例-回文检查
    给select增加placeholder技巧
    易经中人生的六大阶段 :潜、现、惕、跃、飞、亢 你在第几个阶段?
    java 实现傅立叶变换算法 及复数的运算
    java 正则表达式 复习
    关于mysql varchar(N)
  • 原文地址:https://www.cnblogs.com/qixingzhi/p/9468158.html
Copyright © 2020-2023  润新知