HDU2089 不要62

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 ;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值