10.8 数位dp
C++ 数位 DP 代码,以「计算 [0, n] 中不含数字 '4' 和 '7' 的数的个数」为例,采用记忆化搜索实现核心逻辑
把数字拆成一位一位,用递归算每一位能填的合法数字(不含4、7),记着算过的结果避免重复算,最后得出0到n里符合要求的数有多少个。
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
// 全局变量:存储数字位、记忆化数组
vector<int> digits;
int dp[15][2];
// dp[pos][tight]:pos=当前处理位,tight=是否受上界限制(0/1)
// 记忆化搜索:pos=当前位,tight=是否受限,返回[当前位, 最高位]的合法数个数
int dfs(int pos, int tight)
{
if (pos == digits.size()) return 1; // 处理完所有位,算1个合法数
if (dp[pos][tight] != -1) return dp[pos][tight];
int limit = tight ? digits[pos] : 9; // 当前位最大可填数字
int res = 0;
for (int d = 0; d <= limit; d++)
{
if (d == 4 || d == 7) continue; // 跳过不合法数字
// 新的tight状态:原tight为1且当前填了limit,才继续受限
int new_tight = tight && (d == limit);
res += dfs(pos + 1, new_tight); // 递归处理下一位
}
return dp[pos][tight] = res;
}
// 计算[0, x]中合法数的个数
int count_valid(int x)
{
digits.clear();
memset(dp, -1, sizeof(dp));
// 将x拆分为数字位(如x=234 → digits=[2,3,4])
if (x == 0) digits.push_back(0);
while (x) {
digits.push_back(x % 10);
x /= 10;
}
reverse(digits.begin(), digits.end());
return dfs(0, 1); // 从第0位开始
}
int main() {
int n;
cin >> n;
// 计算[L, R]的合法数:count_valid(R) - count_valid(L-1),此处L=0
cout << count_valid(n) << endl;
return 0;
}
核心逻辑说明
1. 数位拆分:将数字 n 拆分为高位到低位的数组(如 234 → [2,3,4] ),便于逐位处理
2. 记忆化状态: dp[pos][tight] 记录「处理到第 pos 位,是否受上界限制」时的合法数个数,避免重复计算
3. DFS 递归:逐位枚举可填数字,跳过不合法数字(如本例的 4、7),根据「是否填到上界」更新 tight 状态,递归累加合法数个数