数位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\n\n",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\n",solve(n) + 1);

44         else    printf("%d\n",solve(n+1));

45     }

46     return 0;

47 }
统计包含'1'的数字的个数

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

 

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

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

 

你可能感兴趣的:(dp)