AcWing 1082. 数字游戏
题目传送门
一、数位DP模板解法
#include
using namespace std;
const int N = 20;
int a[N]; //数位分离的数组
int dp[N][N];//dp[pos][pre]表示当前第pos位,pre是指前一位是什么,这个因素制约了后面的取值个数
/**
* 功能:统计[0~pos]之间答案
* @param pos 当前枚举到的数位pos(搜索的深度)由高到低
* @param pre 前一位是什么
* @param limit 前几位的数字是否等于上界的前几位数字 op(0/1)(限制本次搜索的数位范围)
* @return 方案数
*/
int dfs(int pos, int pre, bool limit) {
//成功到达最后,就是贡献了1个方案
if (pos == -1) return 1;
//记忆化搜索,前提是不贴着上界(可以枚举满这一位所有的数字)
//只有无限制、无前导零才算,不然都是未搜索完的情况
if (!limit && ~dp[pos][pre]) return dp[pos][pre];
//up表示能枚举的最高位数
int res = 0, up = limit ? a[pos] : 9;
for (int i = pre; i <= up; i++)//尝试填充每一个可能的数字
res += dfs(pos - 1, i, limit && i == a[pos]);
//如果不受限制,则记录下来提供下次使用;如果受限制,则一把一利索
return limit ? res : dp[pos][pre] = res;
}
int calc(int x) {
//数位分离
int pos = 0;
while (x) a[pos++] = x % 10, x /= 10; //把x按照进制分解到数组中
return dfs(pos - 1, 0, true);
}
int main() {
int l, r;
//初始化
memset(dp, -1, sizeof dp);
while (cin >> l >> r)
printf("%d\n", calc(r) - calc(l - 1));
return 0;
}
二、DP解法
#include
using namespace std;
const int N = 15;
int f[N][N]; //表示一共有i位,且最高位是j的数的个数
//预处理出不降数的数量数组f[N][N]
void init() {
//1位数的方案数是1
for (int i = 0; i <= 9; i++)f[1][i] = 1;
//从2位数开始求
for (int i = 2; i < N; i++)
for (int j = 0; j <= 9; j++) //最高位填的是j
for (int k = j; k <= 9; k++)//次高位填的是k,需要比j大
f[i][j] += f[i - 1][k]; //枚举次高位即可表示出f[i][j]
}
//计算[0~n]之间不降数的个数
int dp(int n) {
//特判,如果是数字0,那么也是一种方案,因为也不降嘛
if (!n) return 1;
//拆出来每一位
vector nums;
while (n) nums.push_back(n % 10), n /= 10;
int res = 0;
int last = 0;//上一位数字是几,出发的时候从0开始就符合题意
//从高位到低位
for (int i = nums.size() - 1; i >= 0; i--) {
int x = nums[i];
//处理左边分支,也就是可以使用预处理的方式求出来的那一边
for (int j = last; j < x; j++) res += f[i + 1][j];//答案加上一共i+1位,且最高位为j的数的个数
//当前这位能不能填充x
if (x < last) break;//如果降序,直接退出
//更新last
last = x;
//特判处理一下末位,如果成功到达了这句话,就说明每一个数都是大于等于前一个数的
if (!i)res++;
}
//返回结果
return res;
}
int main() {
//结果数组初始化,这次不是组合数就可以搞定了,而是还需要一个DP的过程
init();
int l, r;
while (cin >> l >> r)
cout << dp(r) - dp(l - 1) << endl;
return 0;
}