数位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 以及输出修改一下就能过,果然我的做法是对哦~

原文地址:https://www.cnblogs.com/Newdawn/p/4651865.html