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 ] 就是最后的答案。

浙公网安备 33010602011771号