动态规划

数位DP

数位DP描述的是这样一类问题,在区间[a,b]中,求满足某个条件的数的个数。在空间小的时候,我们可以采用遍历的方式,但是在数据量太大,而且计算较复杂的时候,遍历的方法就会超时,所以我们就引入了数位DP的思想去解决。
在网上给的数位DP的模板中,有两种解题思想。一种是基于DFS,自上而下,记忆化搜索解决这个问题,另一种就是基于动态规划,自下而上去解决这个问题。
下面我们讲解的基于DFS,通过记忆花搜索去解决这个问题。

模板

可以将一个区间看做一棵树,比如对于区间[0,b],b是有 bn1...b0 组成,那么树根节点为一个空节点,那么第一层就为 0...bn1 ,第二层当父节点为 0...bn11 的时候子节点为 0...9 ,父节点为 bn1 的时候子节点为 0...bn2 对于区间[0,b]中所有的元素就是深度优先遍历到所有叶子节点的所有路径。

在往下看之前,一定要理解这个树是什么样子的,下面补一张[0,25]的树的图你会发现从更节点向下DFS遍历的结果就是区间内所有的取值。

这里写图片描述

/*input:
    pos;当前位置
    state;当前状态
    limit;是否收到限制,比如图中的节点2的子节点就受限只能取到05.
*/
int dfs(int pos, int state, bool limit) {
    if (pos==-1) return state==target_state;//如果当前位置为-1,表示遍历到了树的最后一层,如果此时的状态与目标状态相同,那么输出1
    if (!limit && df[pos][state]!=-1) return df[pos][state];//由于dp[pos][state]只记录那些受限制的位置的值,所以必须该位置不受限制才能使用这个值。
    int res = 0;
    int u = limit?num[pos]:9;
    for (int d = 0; d <= u; ++d)
    //判断下下一位是否受限制的标准的是该位受限制,且下一位也受限制,比如三位数635,建树的时候只有当第一层节点为6,第二层节点为3的时候,下一个节点才会受限到0~5;
        res += dfs(pos-1, new_s(s, d), limit&&d==u);
    if(!limit) df[pos][state]=res;//dp[pos][state]只记录那些受限制的位置的值。
    return res;
}

我们在该树下进行DFS遍历,然后通过df数组记录遍历过的节点的值,在之后遇到与之前状态相同的节点时候直接利用前面计算过的数值,就不需要重复计算了,也就是记忆化搜索。

简单的例子

对于[0,25]区间内所有的数字,不包含2的数字有多少个。小学生都知道有18个,但是我们可以用来模拟DP的操作过程,例子简单,没有办法模拟limit的过程了,其实读者可以自己模拟一下,只要将其看成树,理解起来是很简单的。
pos取值:0和1,表示数位。state是我们设置的,一般数位DP比较巧妙的就是如何设置不同的状态,去解决问题。这题我们设置
state:1,前i位有2;0,前i位不存在2

dp[pos][state]:表示的就是pos为满足state的个数。

这里写图片描述

先遍历到最左下角的0,然后dp1=1;然后继续遍历了所有的0的子节点然后dp0=9;于是开始遍历节点1,此时由于节点1的pos也是0而且state也0所以直接返回节点1的值也为9,所以节点1就不需要计算可以直接返回了。讲的不是很清楚,如果看不懂就直接看题。看多了自然会明白了。(核心就是,图中的节点有一些虽然看起来不一样,但是在一些实际问题中,是一样的,比如上题,其实根节点的01两个子树其实本质是一样的,只需要计算一次就可以。)

代码实现

实现包含49的个数。

部分代码:

#include <iostream>
using namespace std;
#define MAXNUM 20
int a[MAXNUM];//储存每一位的最大值
int dp[MAXNUM][3];//0:没有49,且末尾不是41:没有49,且末尾是4,2:是49
int stateTrans(int state,int i){
    //状态转换
    if(state==0&&i==4)//如果当前为状态为0,且当前位为4
        return 1;
    if(state==1&&i==9)
        return 2;
    if(state==1&&i!=4&&i!=9)
        return 0;
    return state;
}
//这道题可以不用pre
int dfs(int pos,int pre,int state,bool limit){

    //计算到最后一位,那么判断state的值
    if(pos==-1) return state==2;
    //如果没有限制,并且已经计算过了,就直接返回对应值
    /*比如在求第5位的时候,如果第五位不是最大值,那么后四位就可以取所有的合法值,
     * 如果第五位去最大值,其对后四位的取值就有影响,也就是后四位不能取遍所有的值0*/
    if(!limit&&dp[pos][state]!=-1) return dp[pos][state]; //记忆化搜索的使用

    int i =0;
    int tmp = 0;
    //pos位所有的可能取值
    for(;i<=a[pos];i++){
        state = stateTrans(state,i);//根据i位状态转换
        tmp+=dfs(pos-1,i,state,limit&&(i==a[pos]));
    }
    if(!limit) dp[i][state]=tmp;
    return tmp;

}
void solve(int x){
    int pos = 0;
    while(x){
        a[pos++] = x%10;
        x = x/10;
    }
    dfs(pos-1,0,0,1);
}

例子

1.比较详细的讲解,其中涉及到如何去确定状态
2.数位DP的两种解法
3.有趣的数,也是要确定满足某个要求的数的个数,但是不是用数位DP,而是使用DP做的。很好的一个题目,解法贴在上面。

——2017.3.16

未完成。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值