HDU2089 不要62(数位DP)
数位dp就是在和数字内部相关性质有关的问题中,
数字太大,暴力求解不行时,把数字拆成一个个位进行动态规划的方法。
举例HDU2089
题意:对于每一组数据,给定区间,求出区间内部不含4,62的数字的个数
基础的数位DP题
首先显然,应该使用前缀和思想,对L,R分别求出从0开始的 pre[R] , pre[L - 1]
对于一个要求的数X,先对X进行拆分,把每一位保存在数组中(num[++len])
从最高位开始dfs搜索 除去含4和62的情况即可求解
但是暴力做法速度过慢,所以我们可以使用记忆化搜索
在本题中4只有一位可以直接判断,而62有两位,因此在dfs函数中应该额外加一个tag
基本思想就是 对于一个数ABCDEFG,计算出从0000000到(A-1)999999的所有合理情况 , 再加上000000到(B-1)99999的合理情况…以此类推,最后的和就是所求的答案
假设这样没有问题,显然求解过程中,会有多个状态重叠,例如3QWER , 4QWER中都会多次去求且不只求出0-29999中符合条件的个数 , 这样会造成时间上的浪费,因此采用记忆化搜索,用 dp[len] 存储len位数字中符合条件的数的个数。
但是这样做会有一个隐蔽的问题,题目中要求的62不能出现。则显然 ABCD6QWER与ABCD5QWER 里面QWER的取值区间不重合,第一个Q不能取2,因此在ABCD6QWER中会影响到
0 ~(0-5)9999中可取的数,dp[len]的正确性无法保证,因此再加一维 is_pre_6
并且注意到 dp[len][is_pre_6] 正确当且仅当后len位已经全部扫描完毕,此时当前的最高位上应该是无限制的(即有取0-9的能力)此时才能更新dp[len][is_pre_6]
同理,只有当当前位有取0-9的能力时才能使用dp[len][is_pre_6]
由此保证dp的正确性
//代码
#include <bits/stdc++.h>
using namespace std ;
typedef long long ll ;
ll dp[20][2] ;
int num[20] ;
int dfs(int len , int is_pre_6 , int have_limit){
if (len == 0) return 1 ;
if (dp[len][is_pre_6] && !have_limit) return dp[len][is_pre_6] ;
int can_test = 0 ;
if (have_limit) can_test = num[len] ;
else can_test = 9 ;
ll ans = 0 ;
for ( int i = 0 ; i <= can_test ; i++ ){
if (i == 4 || (is_pre_6 && i == 2)) continue ;
ans += dfs(len - 1 , i == 6 , have_limit && (i == can_test)) ;//when i == max then have a new limit
}
if (!have_limit) dp[len][is_pre_6] = ans ;
return ans ;
}
long long solve(int tmp){
int len = 0 ;
while (tmp){
num[++len] = tmp % 10 ;
tmp /= 10 ;
}
return dfs(len , 0 , 1) ;
}
int main(){
int m , n ;
while (cin >> n >> m){
if (n == 0 && m == 0) break ;
memset(dp , 0 , sizeof(dp)) ;
memset(num , 0 , sizeof(num)) ;
cout << solve(m) - solve(n - 1) << endl ;
}
return 0 ;
}