zoukankan      html  css  js  c++  java
  • 数位dp——统计'1'的个数

      今天去牛客网看了看 包含一 这道题,一开始没看清,以为它要统计 1~n 所有数中数字 '1' 出现的总次数,也就是说,若 n == 11,则 ans = 4;而按照题目的原意 ans 应该为 3。看错题意后还是挣扎了好久,具体的调试过程也不想回忆叙述了,先贴上按照我一开始理解的意思的代码吧,虽然没有题目让我测,但我和自己写的暴力法对拍过,应该没问题的。

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<vector>
     4 #include<algorithm>
     5 using namespace std;
     6 typedef long long LL;
     7 
     8 LL C[12][12], p9[12] = {1,};
     9 // C 数组为组合数,p9 是 9 的幂数组
    10 inline void initC(const int &n = 11) {
    11     for(int i = 0; i <= n; ++i)
    12         C[i][0] = 1;
    13     for(int i = 1; i <= n; ++i)
    14         for(int j = 1; j <= n; ++j)
    15             C[i][j] = C[i-1][j-1] + C[i-1][j];
    16     for(int i = 1; i <= n; ++i)
    17         p9[i] = p9[i-1] * 9;
    18 }
    19 
    20 LL all[16], num0[16], num1[16];
    21 // all[i] 表示 i 位数的个数,num0[i] 表示不含数字 1 的 i 位数的个数
    22 // num1[i] 表示 i 位数中含有数字 1 出现的总次数,注意是 '1' 出现的总次数!求法有点麻烦
    23 inline void init(const int &k = 11) {
    24     all[0] = 1;
    25     num0[0] = 1;
    26     for(int i = 1; i <= k; ++i) {
    27         all[i] = all[i-1] * 10;
    28         num0[i] = num0[i-1] * 9;
    29     }
    30     initC();
    31     for(int n = 1; n <= k; ++n) {
    32         LL &sum = num1[n];
    33         sum = 0;
    34         for(int i = 1; i <= n; ++i)
    35             sum += i * C[n][i] * p9[n-i];
    36     }
    37 }
    38 
    39 // 和数位 dp 的分析步骤有点类似
    40 inline LL solve(const LL &n) {
    41     LL digit[12], len = 0, x = n;
    42     while(x) {
    43         digit[++len] = x % 10;
    44         x /= 10;
    45     }
    46     int count = 0;      // 统计前 i 位数字 1 的个数
    47     LL sum = 0;
    48     // 核心计数部分(结合曾经做过的数位 dp 的思路来考虑)
    49     for(LL i = len; i > 0; --i) {
    50         sum += digit[i] * num1[i-1];        // 先加上当第 i 位取 0~digit[i]-1 时,i-1 位数的数字 1 的总个数
    51         if(count)    sum += count * digit[i] * all[i-1];    // 统计前面的 1 的个数(第 i 位后取任何数字都没所谓)
    52         if(digit[i] == 1)   ++count;            // 计数器加 1
    53         if(digit[i] > 1)   sum += all[i-1];         // 统计若当前位取 1 时的个数(同理后面是什么都没所谓)
    54     }
    55     // 好好体会下这个数位 dp 的思路,要做到不重不漏真不容易~
    56     return sum;
    57 }
    58 
    59 // 分解统计 '1' 的个数
    60 inline LL caclu(LL x) {
    61     LL res = 0;
    62     while(x) {
    63         if(x % 10 == 1)  ++res;
    64         x /= 10;
    65     }
    66     return res;
    67 }
    68 
    69 // 暴力枚举统计
    70 inline LL test(const LL &x) {
    71     LL sum = 0;
    72     for(LL i = 1; i <= x; ++i)
    73         sum += caclu(i);
    74     return sum;
    75 }
    76 
    77 int main() {
    78     LL n;
    79     init();
    80     while(~scanf("%I64d",&n))
    81         printf("solve = %I64d  test = %I64d
    
    ",solve(n+1),test(n));
    82     return 0;
    83 }
    统计 1~n 中数字'1'出现的总次数

      后来,按照题目的意思我又重做了一遍:

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 using namespace std;
     5 
     6 int dp[3][16];
     7 // dp[0][i] 表示包含 1 的 i 位数的个数
     8 // dp[1][i] 表示以 1 开头的不包含 1 的 i 位数的个数
     9 // dp[2][i] 表示不包含 1 的 i 位数的个数
    10 // 其实 dp[1][i] 和 dp[2][i] 实质是一样的,合并成一个就行了,所以空间和时间都能提升一点;为了让它更直观,我就不改了
    11 inline void init(int n = 11) {
    12     dp[2][0] = 1;
    13     for(int i = 1; i <= n; ++i) {
    14         dp[0][i] = 10 * dp[0][i-1] + dp[2][i-1];
    15         dp[1][i] = dp[2][i-1];
    16         dp[2][i] = dp[2][i-1] * 9;
    17     }
    18 }
    19 
    20 inline int solve(int x) {
    21     int digit[12], len = 0;
    22     while(x) {
    23         digit[++len] = x % 10;
    24         x /= 10;
    25     }
    26     bool flag = 0;
    27     int sum = 0;
    28     for(int i = len; i; --i) {
    29         sum += digit[i] * dp[0][i-1];
    30         if(flag)    sum += digit[i] * dp[2][i-1];
    31         else if(digit[i] > 1)    sum += dp[1][i];
    32         if(digit[i] == 1)   flag = 1;
    33     }
    34     return sum;
    35 }
    36 
    37 const int inf = 0x7fffffff;
    38 
    39 int main() {
    40     int n;
    41     init();
    42     while(~scanf("%d",&n)) {       // 有符号 int 的上限,要注意处理好
    43         if(n == inf)    printf("%d
    ",solve(n) + 1);
    44         else    printf("%d
    ",solve(n+1));
    45     }
    46     return 0;
    47 }
    统计包含'1'的数字的个数

      耗费了一个中午和下午时间写完这两个代码后,我发觉我对于数位 dp 已经感到无爱了,再给我来一道这样的题的话就真的要挂了~

    ---------------------------------------------来填一下坑先---------------------------------------------------

      后来发现,原来还有这样的一道题 统计一,把我第一个代码的 I64d 改为 lld 以及输出修改一下就能过,果然我的做法是对哦~

  • 相关阅读:
    MongoDB4.0以下版本,同一台电脑安装2个MongoDB服务
    CMake编译Mysql connector C++
    Winsock I/O方法
    查看mysql版本的四种方法(转)
    WorkBench,DELETE 标准语句失败
    Qt 透明对话框 自定义透明度
    QString 分割字符串时产生乱码的问题
    winsock error 相关
    线程的分离状态与结合状态
    Oracle 语法
  • 原文地址:https://www.cnblogs.com/Newdawn/p/4651865.html
Copyright © 2011-2022 走看看