数位DP
数位DP描述的是这样一类问题,在区间[a,b]中,求满足某个条件的数的个数。在空间小的时候,我们可以采用遍历的方式,但是在数据量太大,而且计算较复杂的时候,遍历的方法就会超时,所以我们就引入了数位DP的思想去解决。
在网上给的数位DP的模板中,有两种解题思想。一种是基于DFS,自上而下,记忆化搜索解决这个问题,另一种就是基于动态规划,自下而上去解决这个问题。
下面我们讲解的基于DFS,通过记忆花搜索去解决这个问题。
模板
可以将一个区间看做一棵树,比如对于区间[0,b],b是有 bn−1...b0 组成,那么树根节点为一个空节点,那么第一层就为 0...bn−1 ,第二层当父节点为 0...bn−1−1 的时候子节点为 0...9 ,父节点为 bn−1 的时候子节点为 0...bn−2 对于区间[0,b]中所有的元素就是深度优先遍历到所有叶子节点的所有路径。
在往下看之前,一定要理解这个树是什么样子的,下面补一张[0,25]的树的图你会发现从更节点向下DFS遍历的结果就是区间内所有的取值。
/*input:
pos;当前位置
state;当前状态
limit;是否收到限制,比如图中的节点2的子节点就受限只能取到0~5.
*/
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,且末尾不是4,1:没有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
未完成。