hdu3555 Bomb ——數位DP入門題


題目鏈接:http://acm.hdu.edu.cn/showproblem.php?pid=3555

題目大意:

  給一個數字n,范圍在1~2^63-1,求1~n之間含有49的數字有多少個。

思路:

  經典的數位DP,學習了一下,看的別人的代碼:http://www.cnblogs.com/luyi0619/archive/2011/04/29/2033117.html

  狀態轉移:

  dp[i][0]代表長度為 i 並且不含有49的數字的個數;

  dp[i][1]代表長度為 i 並且不含有49,但是最高位是9的數字的個數;

  dp[i][2]代表長度為 i 並且含有49的數字的個數。

  數組 a[i] 從低位到高位存儲 n 的每一位數字。

  則:dp[i][0] = dp[i-1][0] * a[i] - dp[i-1][1];  表示長度為 i 的不含有49的數字的個數等於長度為 i - 1 的不含有49的數字的個數*當前的數字,因為這個位置可以填0~a[i] - 1,然后再減去長度為 i - 1 的最高位是9的數字的個數,因為如果長度為 i - 1 的最高位是9的話,那么高一位就不能填4了,否則就組成了49。

    dp[i][1] = dp[i-1][0]; 表示長度為 i 的並且不含有49同時最高位是9的數字的個數等於,長度為 i - 1 的不含有49的數字的個數,因為只要在它的高一位加上一個9就可以了。

    dp[i][2] = dp[i-1][2] * a[i] + dp[i-1][1]; 表示長度為 i 的含有49的數字的個數等於,長度為 i - 1 的數字的個數*當前的數字,再加上長度為 i - 1 的並且不含有49同時最高位是9的數字的個數,因為這個時候,只要在高一位加上一個4就可以了,這樣在最高的兩位就組成了一個49。

  做法是從數字的高位向低位掃描,對於第 i 位,

  1.   首先加上長度為 i - 1 的符合條件的數字個數;
  2.   再討論以前是不是出現過49,如果出現過,就要再追加上長度為 i - 1 的不符合條件的數字的個數,因為以前已經有49了;
  3.   如果沒有出現過,就要判斷這一位是不是大於4呢,如果大於4,就要再追加上長度為 i - 1 的不含有49但是最高位是9的數字的個數,因為這個時候可以再這一位填4,因為它大於4嘛~;
  4.   然后就是判斷一下,當前位和上一位是不是滿足49,如果滿足,標記出現了49了!為以后的判斷做准備。

  其實這個題目還有一個地方不懂,就是為什么要在輸入 n 后,要把 n 加1。想了一下特例,比如輸入49,按照上面的做法,在第3步,並不會把符合條件的數字加上,因為4不是嚴格大於4,最后的執行結果就是0,但是如果加上1之后,n就變成了50,這樣第3步恰好可以執行,結果就是正確的了。但是對於一般的情況,還是不知道為什么要把n加1……o(╯□╰)o

  這題還是卡了很久,照着別人的代碼敲的,死活過不了,然后又找了一份代碼:http://blog.csdn.net/acm_cxlove/article/details/7819907 才發現輸入輸出要用%I64d,這不是坑么……原來hdu要用%I64d,囧……

  所以,有時候的bug不是算法或者代碼有錯誤,看看你的輸入輸出吧!還有,類似的情況,比如,輸入文件寫錯了……更悲劇了。。

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstdlib>
 4 #include <cstring>
 5 #include <cctype>
 6 #include <stack>
 7 #include <queue>
 8 #include <cmath>
 9 #include <algorithm>
10 #define lson l, m, rt<<1
11 #define rson m+1, r, rt<<1|1
12 using namespace std;
13 typedef long long int LL;
14 const int MAXN =  0x3f3f3f3f;
15 const int  MIN =  -0x3f3f3f3f;
16 const double eps = 1e-9;
17 const int dir[8][2] = {{0,1},{1,0},{0,-1},{-1,0},{-1,1},
18   {1,1},{1,-1},{-1,-1}};
19 LL dp[21][3]; unsigned long long int n; int a[25];
20 int main(void){
21 #ifndef ONLINE_JUDGE
22   freopen("hdu3555.in", "r", stdin);
23 #endif
24   int t; scanf("%d", &t);
25   memset(dp, 0, sizeof(dp));
26   dp[0][0] = 1;
27   for (int i = 1; i < 21; ++i){
28     dp[i][0] = dp[i-1][0] * 10 - dp[i-1][1];
29     dp[i][1] = dp[i-1][0];
30     dp[i][2] = dp[i-1][2] * 10 + dp[i-1][1];
31   }
32   while (t--){
33     //cin >> n;
34     scanf("%I64d", &n);
35     int len = 0; memset(a, 0, sizeof(a));
36     n++;
37     while (n){
38       a[++len] = n % 10; n /= 10;
39     } LL ans = 0; int last = 0; bool flag = false;
40     for (int i = len; i >= 1; --i){
41       ans += (dp[i-1][2] * a[i]);
42       if (flag) ans += dp[i-1][0] * a[i];
43       if (!flag && a[i] > 4) {ans += dp[i-1][1];}
44       if (last == 4 && a[i] == 9) {flag = true;}
45       last = a[i];
46     }
47     //cout << ans << endl;
48     printf("%I64d\n", ans);
49   }
50 
51   return 0;
52 }

  就為了輸入輸出,糾結好久……所以有的時候還是用cin,cout當輸入量不大的時候……

  還是不懂為什么要把 n 加 1,繼續想想,,神牛路過明白的解釋一下撒~感激不盡……

  貌似有點兒想明白了……也許加 1 的目的就是處理 n 末兩位是49的這種特殊情況,如果最后兩位不是49,也不影響結果。比如,如果n是149,變成150,結果是1+1 = 2;如果n是123,變成124,結果依然是1;可是,如果要找的是65這種的呢?需要加5么?如果要統計的是一個三位數呢?比如找1~n里面含有123的數字的個數……該怎么做……

  做了后面幾道題目,明白了為什么要n++,因為后面的for循環求的是(0,n)的開區間的符合條件的數字的數目,題目要求[1,n]這個區間內的符合條件的數字的數目,所以要把區間的右端點加1。這樣的處理方式比較方便,就不用判斷這個端點是不是如何條件的數字了。膜拜神牛的想法……


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM