简介
数位DP是一类非常精巧的动态规划,它解决的问题是 (0,r] 区间上满足某种性质的数的计数问题。例如,求 (0,468] 中数位中没有” 49 ”的数的个数。动态规划的关键是找到重叠子问题和最优子结构,为此数位DP将区间的右端点 r 看作是字符串,例如 468 被当做”468”处理。显然 468 是个 3 位数,比这个数小的数有,
000 | 001 | ⋯⋯ | 099 |
---|---|---|---|
100 | 101 | ⋯⋯ | 199 |
200 | 201 | ⋯⋯ | 299 |
300 | 301 | ⋯⋯ | 399 |
400 | 401 | ⋯⋯ | 469 |
显然,如果我们能够逐段求出答案就可以找到最终答案。如何巧妙地组织这个解空间,然后逐步求解呢?
可以按照下图组织:
上图中存在比较多的重叠子问题,可以使用动态规划来解决;需要特别强调的是如果百位为4或者百位为4且十位为6的时候,其对应的子问题,不具有通用性,不应该备忘。上述条件被称为limit条件。
引入
请计算 [0,234] 中不包含 49 的数字的个数。
如何求解这个问题呢?
首先看百位上的情况,百位一定是受限制的,其数值只能取0到4,而不是0到9。我们来考虑百位为0、1的情况,只需计算000到099中不包行49的数的个数就好了;当考虑百位为2时,情况变得复杂起来,其十位只能考虑0、1、2、3,而非0到9。百位为0、1不满足limit条件,其对应的子问题(00到99中不含49的数)有一定的通用性需要记录,以便复用。
上代码
#include<map>
#include<vector>
#include<unordered_set>
#include<unordered_map>
#include<list>
#include<queue>
#include<deque>
#include<stack>
#include<iostream>
#include<set>
using namespace std;
const int MAX_LEN = sizeof(int) / sizeof(char) * 8;
class Solution {
public:
Solution(int x) {
this->x = x;
memset(dp, (char)(-1), sizeof(dp));
}
int work() {
string d = this->init();
return this->dfs((int)d.size() - 1, d, true, 0);
}
private:
string init() {
string d;
int x = this->x;
while (x) {
int tmp = x % 10;
x /= 10;
d.push_back(tmp);
}
return d;
}
int dfs(int pos, string& d, bool limit, int state) {
if (pos == -1) {
if (state == 49) {
return 1;
} else {
return 0;
}
}
if (!limit && dp[pos][state] != -1) return dp[pos][state];
int maxNum = limit ? d[pos] : 9;
int cnt = 0;
for (int dNum = 0; dNum <= maxNum; ++dNum) {
if (state == 4 && dNum == 9) {
cnt += dfs(pos - 1, d, limit&&d[pos]==dNum, 49);
} else if (state == 49) {
cnt += dfs(pos - 1, d, limit&&d[pos]==dNum, 49);
} else {
cnt += dfs(pos - 1, d, limit&&d[pos]==dNum, dNum == 4 ? 4 : 0);
}
}
if (!limit) {
dp[pos][state] = cnt;
}
return cnt;
}
int x;
int dp[MAX_LEN][2];
};
int main() {
int x;
cin>>x;
Solution s(x);
cout<<"result:\t";
cout<<s.work()<<endl;
return 0;
}