简介
数位DP是一种计数的DP,一般就是要统计一个区间[l,r]内满足一些条件的个数。所谓数位DP, 意思就是在数位(个位,十位,百位,,,)上进行DP。
对于这种问题的求解思路:先得出解ans[x]([1,x]区间内满足条件的个数)的方法,利用前缀和的思路,最终的解为ans[r] - ans[l-1]。
问题的关键是:怎么求ans[x]?
我们采用枚举的方式,控制上界枚举,从最高位开始往下枚举,例如: x = 213,那么我们从百位枚举,百位的可能情况是0,1,2,
当百位是1 的时候,十位和个位都是从0到9;当百位是2的时候,十位就从0到1。。。。总之不要枚举超过上界213就可以了。
例题---600. 不含连续1的非负整数
给定一个正整数 n,找出小于或等于 n 的非负整数中,其二进制表示不包含 连续的1 的个数。
class Solution {
public:
int a[111];
int dp[33][2];
int dfs(int pos,int state,bool limit)
{
if(pos == -1)return 1;
if(!limit && dp[pos][state] != -1)return dp[pos][state];//未达到上限,记忆化
int up = limit?a[pos]:1;
int sum = 0;
for(int i = 0;i<=up;++i)
{
if(state && i == 1)continue;
sum += dfs(pos-1,i==1,limit && i == up);
}
if(!limit)dp[pos][state] = sum;//记忆化
return sum;
}
int solve(int x)
{
int pos = 0;
while(x)
{
a[pos++] = x&1;
x >>= 1;
}
return dfs(pos-1,0,1);
}
int findIntegers(int num) {
memset(dp,-1,sizeof(dp));
return solve(num);
}
};
例题---不要62 HDU - 2089
题意:给定一个区间,求出“吉利数”的个数。吉利数:不含62或4
#include<bits/stdc++.h>
using namespace std;
int a[22];
int dp[22][2];
int dfs(int pos,int state,bool limit)
{
if(pos == -1) return 1;
if(!limit && dp[pos][state] != -1)return dp[pos][state];
int up = limit?a[pos]:9;
int sum = 0;
for(int i = 0;i <= up;++i)
{
if(state && i == 2)continue;
if(i == 4)continue;
sum += dfs(pos-1,i==6,limit && i == up);
}
if(!limit)dp[pos][state] = sum;
return sum;
}
int solve(int x)
{
int pos = 0;
while(x)
{
a[pos++] = x%10;
x /= 10;
}
return dfs(pos-1,0,true);
}
int main()
{
int l,r;
while(cin>>l>>r && l + r)
{
memset(dp,-1,sizeof dp);
cout << solve(r) - solve(l-1) << endl;
}
}
例题---整数中1出现的次数(从1到n整数中1出现的次数)
leetcode233. 数字 1 的个数
class Solution {
public:
int dp[20][20],num[20],cnt=0;
int dfs(int pos,int flag,int sum){//sum表示pos位前面有几个1
if(pos == -1)return sum;
if(!flag && ~dp[pos][sum])return dp[pos][sum];//dp[pos][sum]表示到第pos位之前有sum个1的记忆搜索值是多少
int up = flag? num[pos]:9,ans = 0;
for(int i = 0;i<=up;++i){
ans+=dfs(pos-1,flag && (i == up),sum+(i==1));
}
if(!flag)dp[pos][sum] = ans;
return ans;
}
int cal(int x){
while(x){
num[cnt++]=x%10;
x/=10;
}
return dfs(cnt-1,1,0);
}
int countDigitOne(int n) {
if(!n)return 0;
memset(dp,-1,sizeof dp);
return cal(n);
}
};
//数学方法,数学方法蛮好理解的,写个数字推一下就好了。
class Solution {
public:
int NumberOf1Between1AndN_Solution(int n)
{
if(n<1)return 0;
long long ans = 0;
long long round = n;
long long base = 1;
while(round){
int w = round % 10;
round /= 10;
ans += round * base;
if(w == 0)ans += 0;
else if(w == 1)ans += (n%base)+1;
else ans += base;
base *= 10;
}
return ans;
}
};