Q / R - 不要62 / Bomb(数位dp)题解

这两个题都是很标准的数位DP的题,而且非常相似,所以我将这两个题放在一起。

首先来讲一下数位DP或者说数位DP的写法 / 技巧(板子,都是板子!!!):

1、绝大多数的数位DP都是给我们一个区间 [ x ,  y ] 让我们去求这个区间符合题目要求的数的个数;

而我们的处理方式大多是像前缀和一样,预处理出 [ 0,  N ] 的值,区间 [ x ,  y ] == f ( y ) - f ( x -1 ) ;

2、(这个是看了 acwing 上 y 总讲的,y 总 yyds !!!)从树的角度去考虑DP:
举个栗子:N = 7854421,我们讲 N 这个数看成是由 7,8,5,4,4,2,1 个只有一位数的数字构成,我们按顺序把前面7个数分别看成 a ( 7 ) , a ( 6 ) , a ( 3 ) …… a ( 1 ) ;

f ( i ,  j ) 表示填写第 i 个数的时候,数的状态为 j (此时填的数是 j );

 ** 这里的 0 ~ a1 应该是 0 ~ (a1 - 1 ) 这里画错了

 

 我们像上图一样,将每个位置都分成两种情况,一种是填 0 ~ ( a[ i ] - 1 ) 另一种是填 a[ i ] ;

直接上题分析板子吧。

[Q题传送门](Problem - 2089 (hdu.edu.cn))

题目大意:

当一个数字里面含有 62 或者 4 则认为这个数字是不吉利的,给定两个数,求这个区间里有多少个吉利的数。

 初始化:

因为我们需要去考虑 62 的情况所以我们在 for ( i ) 的里面套两层循环,一个表示第 i 位,另一个表示第 i - 1 位;

** f [ i ] == f [ i ] [ 0 ~ a[ i ] ] ,f [ i ] [ j ] == f [ i - 1 ],理解这个状态方程就能理解 for ( k ) f [ i ] [ j ] += f [ i - 1 ] [ k ] ;

 1 void init() // dp初始化
 2 {
 3     for (int i = 0; i <= 9; i++)
 4         f[1][i] = 1; 
 5     f[1][4] = 0; // 先初始化一位,因为只有一位所以我们只需要去考虑等于 4 的情况
 6     for (int i = 2; i <= N; i++) //从第2位开始填
 7     {
 8         for (int j = 0; j <= 9; j++) // j表示第i位填的数
 9         {
10             for (int k = 0; k <= 9; k++) // k表示第i - 1位填的数
11             {
12                 if (k == 4 || j == 4)
13                     continue;
14                 if (j == 6 && k == 2)
15                     continue;
16                 f[i][j] += f[i - 1][k];
17             }
18         }
19     }
20 }

初始化之后就是DP过程:

 1 int dp(int x)
 2 {
 3     if (!x)//边界判断
 4         return 1;
 5     int a[12]; //存储车牌上的每一位数
 6     int l = 0;
 7     int ans = 0;
 8     int last = 0; //用来存储上一位数,用来判断连续的62是否存在
 9     while (x)
10     {
11         a[++l] = x % 10;
12         x /= 10;
13     }
14     for (int i = l; i >= 1; i--)
15     {
16         for (int j = 0; j < a[i]; j++)
17         {
18             if (j == 4)
19                 continue;
20             if (last == 6 && j == 2)
21                 continue;
         //题目附加条件
22 ans += f[i][j]; 23 } 24 if (a[i] == 4) 25 break; 26 if (a[i] == 2 && last == 6) 27 break;
       //题目附加条件
28 last = a[i]; 29 if (i == 1) 30 ans++; 31 } 32 return ans;//返回的是 0 ~ x 中符合题目条件的数的个数 33 }

按照类似于前缀和的方法就可以求出区间 [ x ,  y ] == f ( y ) - f ( x -1 ) ;

**我们发现 for( j = 0; j < a[ i ] ; j++ ) 以及最开始对只有一位时的初始化都是从选的数从 0 开始的,所以我们在这里求的区间和其实是 0 到目前这个数的个数。

完整代码如下:

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <algorithm>
 4 #include <vector>
 5 #include <cmath>
 6 #include <cstring>
 7 #include <queue>
 8 #include <map>
 9 #include <unordered_map>
10 #include <set>
11 #define ll long long
12 #define ull unsigned long long
13 #define ci const int
14 #define PLL pair<ll, ll>
15 #define PII pair<int, int>
16 #define PSI pair<string, int>
17 #define MAX1 0x3f3f3f3f
18 #define lowbit(x) x &(-x)
19 using namespace std;
20 ci N = 10;
21 int f[N][N]; //从头开始填数
22 // f[i][j]表示填到第i位时候,第i位填j的状态的种数
23 
24 void init() // dp初始化
25 {
26     for (int i = 0; i <= 9; i++)
27         f[1][i] = 1;
28     f[1][4] = 0;
29     for (int i = 2; i <= N; i++) //从第2位开始填
30     {
31         for (int j = 0; j <= 9; j++) // j表示第i位填的数
32         {
33             for (int k = 0; k <= 9; k++) // k表示第i - 1位填的数
34             {
35                 if (k == 4 || j == 4)
36                     continue;
37                 if (j == 6 && k == 2)
38                     continue;
39                 f[i][j] += f[i - 1][k];
40             }
41         }
42     }
43 }
44 int dp(int x)
45 {
46     if (!x)
47         return 1;
48     int a[12]; //存储车排的每一位数
49     int l = 0;
50     int ans = 0;
51     int last = 0; //用来存储上一位数,用来判断连续的62是否存在
52     while (x)
53     {
54         a[++l] = x % 10;
55         x /= 10;
56     }
57     for (int i = l; i >= 1; i--)
58     {
59         for (int j = 0; j < a[i]; j++)
60         {
61             if (j == 4)
62                 continue;
63             if (last == 6 && j == 2)
64                 continue;
65             ans += f[i][j];
66         }
67         if (a[i] == 4)
68             break;
69         if (a[i] == 2 && last == 6)
70             break;
71         last = a[i];
72         if (i == 1)
73             ans++;
74     }
75     return ans;
76 }
77 int main()
78 {
79     init();
80     int l, r;
81     while (cin >> l >> r && (l | r))// l != 0 && r != 0
82         cout << dp(r) - dp(l - 1) << "\n";
83     return 0;
84 }

[R题传送门](Problem - 3555 (hdu.edu.cn))

题目大意:

在 1 ~ N 的区间中如果有一个数中有连续的 49 则加一分,输入 N 值,求该区间的分数。

仔细读题后会发现 R 题和 Q 题非常相似我们则可以参照 Q 题的思路求 1 ~ N 区间没有 49 的数的个数,然后输出 N - dp [ N ] 就是最后的答案。

posted @ 2022-07-07 22:21  aYi_7  阅读(101)  评论(0)    收藏  举报