HDU2089 不要62【数位DP+记忆化搜索】

不要62

Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 57271    Accepted Submission(s): 22409


 

Problem Description

杭州人称那些傻乎乎粘嗒嗒的人为62(音:laoer)。
杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除个别的士司机和乘客的心理障碍,更安全地服务大众。
不吉利的数字为所有含有4或62的号码。例如:
62315 73418 88914
都属于不吉利号码。但是,61152虽然含有6和2,但不是62连号,所以不属于不吉利数字之列。
你的任务是,对于每次给出的一个牌照区间号,推断出交管局今次又要实际上给多少辆新的士车上牌照了。

 

 

Input

输入的都是整数对n、m(0<n≤m<1000000),如果遇到都是0的整数对,则输入结束。

 

 

Output

对于每个整数对,输出一个不含有不吉利数字的统计个数,该数值占一行位置。

 

 

Sample Input

1 100 0 0

 

 

Sample Output

80

 

 

Author

qianneng

 

 

Source

迎接新学期——超级Easy版热身赛

 

问题链接HDU2089 不要62

问题简述:(略)

问题分析

  这是一个数位DP问题。

  数位DP一般应用于求出在给定区间[A,B]内,符合条件P(i)的数i的个数,条件P(i)一般与数的大小无关,而与数的组成有关。

  实际计算时,可以采用记忆化搜索实现,已经搜索过的就不再搜索。这种计算也可以称为记忆化计算,已经计算过的就不再计算,可以避免重复的计算,加快计算速度。

  数位DP可以采用直接搜索和记忆化搜索两种方式来处理。

程序说明

  记忆化搜索:

  设计数组dp[][]是一个关键!不同的问题略有不同,有经验后就简单了。

  一种定义是dp[i][0 / 1] 表示不含 4 和 62的前提下,剩余长度为i,首位是否为 6 的个数。初值为-1。

  另外一种定义是dp[i][d]表示共i位,前导为数字d的满足条件(不含62和4)数的数量。

  不同的数组dp[][]定义,使得程序的逻辑不一样。采用后一种定义,程序逻辑要简单许多。

  数组dp[][]的元素初始化为-1,表示其值尚未计算得到,需要用函数dfs()进行计算。初始化应该放在主函数中循环处理之前进行,可以最大限度避免重复计算。

  函数solve(n)的功能是计算(0,n]的满足条件的数的个数。做法是将n的各位分解成数字位0-9,放入数组digits[]中,个位放在digits[0]中,即低位放在下标小的数组元素中,高位放在下标大的数组元素中。然后通过深度优先搜索函数dfs(),根据数组digits[]指定的数去搜索。

  有关limit变量,以n=5676为例,简单说明如下:

  1.开始时从最高位开始搜索,即从千位5开始,可取的数字只能是0-5(首次调用函数limit的实参是1,即只能取0-5);

  2.千位若取0-4,百位可取的值则为0-9;

  3.千位若取5,百位可取的值就只能取1-6(6是n的百位数字);

  4.根据前2条,就用参数limit来控制下一个数位的取值范围,在搜索的时候就看是否i==digits[pos],不等的话limit取值false,下一位(低一位)取值范围则为0-9,否则limit取值true,下一位(低一位)取值范围则为0-x,x为下一位的数字。

题记:(略)

参考链接:(略)

 

AC的C++语言程序(数位DP+记忆化搜索,简洁易懂)如下:

/* HDU2089 不要62 */

#include <bits/stdc++.h>

using namespace std;

const int N = 20;  // 位数,int类型不超过10位
const int D = 10;  // 10进制数字的个数
int digits[N + 1];
int dp[N][D];  // dp[i][d]-共i位,前导为数字d的满足条件(不含62和4)数的数量

/*
 * 参数:
 * pos - 数位位置,即当前处理数的第几位,从高位开始
 * pre - 前导,即前一位数字
 * limit - 是否为数位上界(最大数字)
 */
int dfs(int pos, int pre, bool limit)
{
    if(pos == -1)   // 递归边界,已经枚举结束,则1个数满足条件
        return 1;
    if(!limit && dp[pos][pre] != -1)  // 已经搜索过的不再搜索,直接使用之前的计算结果
        return dp[pos][pre];

    // 计数
    int ans = 0;
    int maxd = limit ? digits[pos] : 9;  // 枚举数字,如果数字不同则枚举0-9
    for(int i = 0; i <= maxd; i++) {
        if(i == 4 || (pre == 6 && i == 2))
            ;
        else
            ans += dfs(pos - 1, i, limit && i == digits[pos]);
    }
    if(!limit)
        dp[pos][pre] = ans;

    return ans;
}

// 计算[0,n]中不含62和4数的数量
int solve(int n)
{
    int len = 0;
    while(n) {
        digits[len++] = n % 10;
        n /= 10;
    }
    return dfs(len - 1, 0, 1);
}

int main()
{
    memset(dp, -1, sizeof(dp));

    int n, m;
    while(~scanf("%d%d", &n, &m) && (n || m))
        printf("%d\n", solve(m) - solve(n - 1));

    return 0;
}

 

AC的C++语言程序(数位DP+记忆化搜索)如下:

/* HDU2089 不要62 */

#include <bits/stdc++.h>

using namespace std;

const int N = 10;
int dp[N][2], digits[N + 1];

/*
 * 参数:
 * pos - 数位位置,即当前处理数的第几位,从高位开始
 * pre - 前导,即前一位数字
 * state - 状态,dp[i][state]表示剩余长度为i,首位是否包含指定数字(6)
 * limit - 数位上界,即最大数字
 */
int dfs(int pos, int pre, int state, bool limit)
{
    if(pos == -1)   // 递归边界,已经枚举结束,这个数是合法的,1个
        return 1;
    if(!limit && dp[pos][state] != -1)  // 已经搜索过的不再搜索,直接使用之前的计算结果
        return dp[pos][state];

    // 计数
    int ans = 0;
    int maxd = limit ? digits[pos] : 9;  // 枚举数字,如果数字不同则枚举0-9
    for(int i = 0; i <= maxd; i++) {
        if(i == 4 || (pre == 6 && i == 2))
            continue;
        ans += dfs(pos - 1, i, i == 6, limit && i == digits[pos]);
    }
    if(!limit)
        dp[pos][state] = ans;

    return ans;
}

int solve(int n)
{
    int len = 0;
    while(n) {
        digits[len++] = n % 10;
        n /= 10;
    }
    return dfs(len - 1, - 1, 0, 1);
}

int main()
{
    int n, m;
    memset(dp, -1, sizeof(dp));
    while(~scanf("%d%d", &n, &m) && (n || m))
        printf("%d\n", solve(m) - solve(n -1));

    return 0;
}

 

AC的C++语言程序(数位DP)如下:

/* HDU2089 不要62 */

#include <iostream>
#include <stdio.h>

using namespace std;

const int M = 6;
const int N = 10;
int dp[N][N], digits[N + 1]; // dp[i][j]表示i位数,其中首位为数字j的数量

void init()
{
    dp[0][0] = 1;
    for (int i = 1; i <= M; i++)
        for (int j = 0; j < N; j++)
            for (int k = 0; k < N; k++)
                if (j != 4 && !(j == 6 && k == 2))
                    dp[i][j] += dp[i - 1][k];
}

int solve(int n)
{
    int len = 0;
    while(n) {
        digits[++len] = n % 10;
        n /= 10;
    }
    digits[len + 1] = 0;

    int ans = 0;
    for(int i = len; i >= 1; i--) {
        for(int j = 0; j < digits[i]; j++) {
            if(digits[i + 1] != 6 || j != 2)
                ans += dp[i][j];
        }
        if(digits[i] == 4 || (digits[i + 1] == 6 && digits[i] == 2))
            break;
    }

    return ans;
}

int main()
{
    init();

    int n, m;
    while(~scanf("%d%d", &n, &m) && (n || m))
        printf("%d\n", solve(m + 1) - solve(n));

    return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值