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

这段代码是解决一个问题的递推算法。问题的描述是:给定一个闭区间 [m, n],其中 m 和 n 是两个正整数,统计该区间内满足特定条件的数字个数。 首先,代码中的 init 函数用于初始化一个二维数组 dp。dp[i][j] 表示第一位为 j 的 i 位数中满足条件的数字数量。初始化时,将 dp[0][0] 设置为 1,表示只有一个位数且为 0,满足条件的数字个数为 1。 然后,通过嵌套循环来计算 dp 数组的其他元素。外层循环遍历位数 i,内层两个循环遍历第 i 位数的可能取值 j 和前一位数的可能取值 k。在遍历过程中,根据特定条件判断,如果满足条件,则将 dp[i][j] 累加上 dp[i-1][k] 的值。 接下来,solve 函数用于计算闭区间 [0, n) 中满足条件的数字个数。首先,将数字 n 拆分成位数,并保存在 digit 数组中。然后,从高位到低位遍历 digit 数组。对于第 i 位数 digit[i],通过嵌套循环来计算满足条件的数字个数。内层循环遍历从 0 到 digit[i]-1 的可能取值 j,根据特定条件判断,如果满足条件,则将答案 ans 加上 dp[i][j] 的值。 在循环过程中,如果第 i 位数 digit[i] 等于 4,则表示以 4 开头的数字后面的数字不满足条件,因此可以直接跳出循环。如果第 i 位数 digit[i] 等于 2 且下一位数 digit[i+1] 等于 6,则表示以 62 开头的数字后面的数字也不满足条件,可以直接跳出循环。 最后,在主函数中,通过调用 init 函数来初始化 dp 数组。然后,通过循环读入输入的 m 和 n 的值,直到 m 和 n 都为 0 时结束循环。在每次循环中,计算闭区间 [m, n] 内满足条件的数字个数,即 solve(m+1) - solve(n),并输出结果。 这段代码利用动态规划的思想,通过递推关系计算满足条件的数字个数,从而高效地解决了给定的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值