数位DP总结

题目类型

数位DP题目往往呈现出如下题目特征:
给定一个闭区间[L,R], 求这个区间中满足某个条件的数的总数量

题目示例

某人命名了一种不降数, 这种数字必须满足从左到右各位数字呈非下降关系, 如123, 446
现在大家决定玩一个游戏, 指定一个整数闭区间[a,b], 问这个区间内有多少个不降数.

解决思路

1.转换范围

首先把统计[L,R]范围内满足条件的数字转化成统计[0,N]内满足条件的数字数量.则ans [L,R] = ans[0,R] - ans[0,L-1]
这样就把双边界改为了只需要考虑上边界
(有人这里使用1作为下界也可以,我更习惯于使用0)

2.将数字长度按最大边界补全前导0

这一步的功能在于建立统一数据格式,便于在算法中连续推进.这个应该比较好理解, 处理定长数据肯定比处理变长数据容易些.

3.从高位到低位枚举,建立初始dfs框架

枚举过程中,我们分别要记录

1.当前枚举到哪一位置 (这个不用过多解释)

2.前面一位的数字是多少 (前面一位数字, 决定了当前位置的最小值, 在这里是因为题目要求如此, 因此需要记录, 不同题目记录的可能有变化)

3.这一位可以填写哪些数字 (取决于前一位是否达到最大值, 如果取到最大值, 则只能填写大于等于前值 且 不超过最大值的范围内数字; 否则 可以取到大于等于前值 且 不超过9的范围内数字)
举个例子, 比如 123 这个数字, 当我们进行到十位时, 如果百位取的是0, 则十位可以取到[0,9]; 如果百位取的是1, 则十位可以取到[1,2] . 因为边界是123, 不能超过这个数字.
实际操作中呢, 因为前一位数字我们已经记录了, 因此用一个布尔值记录前一位是否取到最大值, 来作为选择依据

一个简单的dfs示例如下:

int dfs(int cur,int pre,bool flag){
	//如果超过数位size, 说明已经找到一个答案, 则返回1
    if(cur>=m){
        return 1;
    }

    int res = 0;
    
    for(int i=pre;i<=9;i++){ //因为不小于前一位,所以从前一位数字开始循环
        if(flag){ // 如果前一位取到最大值
            if(i>max_nums[cur]){ //当前选择超过最大值,则直接跳出
                break;
            }
            res += dfs(cur+1,i,flag&&(i==max_nums[cur])); //向下一数位跳动
        }
        else{
            res += dfs(cur+1,i,false); //前一位未取到最大值,则随意蹦跶
        }
    }
    
    return res;
}

4.dp概念引入

dp的主要思想是空间换时间, 利用存储一些计算过的数据, 节省后续重复时间
因此在这里, 我们引入记忆化存储空间, 记录dp[cur][pre]的结果,表示到达cur位置, 前一位置取pre的结果数.

为什么是这样使用, 而不是像传统dp那样使用呢,因为数位dp的使用有限制条件,在前一位未取到最大值时可以用. 因此需要加以判断, 这里是通过dfs记忆化搜索的形式便于理解.

int dfs(int cur,int pre,bool flag){
    if(cur>=m){
        return 1;
    }

    int res = 0;
	
	//新引入内容,若前一位未取到最大值,且之前计算过,则直接返回
    if(!flag&&dp[cur][pre]!=-1)
        return dp[cur][pre];
    
    for(int i=pre;i<=9;i++){
        if(flag){
            if(i>max_nums[cur]){
                break;
            }
            res += dfs(cur+1,i,flag&&(i==max_nums[cur]));
        }
        else{
            res += dfs(cur+1,i,false);
        }
    }
	
	//记忆化存储
    if(!flag)
        dp[cur][pre] = res;
    
    return res;
}

这里需要重点理解一下,为什么是这样写

5.初始状态的设置

示例输入 1, 19 进行计算

	int m = 2;
    vector<int> max_nums={0,0};
    vector<vector<int>> dp(m,vector<int>(10,-1));
    int lres = dfs(0,0,true);
    max_nums={1,9};
    int rres = dfs(0,0,true);
    return rres-lres;

注意, 初始状态一定是true, 因为-1位的最大值就是0, 这样后续有效开展计算.

代码和分析是自己写的, 如果有错误也请及时指出, 大家互相交流.
最后附一个别人写的, 更接近与传统dp写法的版本, 大家综合学习吧
点击这里跳转

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值