HDU2089_不要62_数位DP

题意

包含 4 或 62 字段的数字称为不吉利数字。统计 [n, m] 中吉利数字的数目。

思路

数位DP问题。
dp[i][j] (j 取 0 ~ 2)表示 [0, 10^i)区间内 吉利数的个数、吉利但第一位是2、不吉利的个数。转移方程不难写出,见AC代码部分注释即可。

关键的地方在于怎么处理 [n, m] 的限制。也是在这个题中学到的地方。这里要感谢百度文库里一个ppt,讲解的很清晰。

首先把 [n, m] 转化位 [0, m + 1) - [0, n)。
对于一个小于 n 的数,肯定是从高位到低位出现某一 < n 的对应位。如果某一位比 n 的对应位小,则后面的位可以 0 ~ 9 而没有限制。具体做法就是,枚举位 i ,前面的位固定等于 n 的对应位,后面的位则可以取到 0 ~ 9 而没有限制。

注意的几个点:
1.dp转移的方向和区间统计的方向是相反的。具体到本题中,dp转移方向从低位向高位,区间统计方向从高位向低位。
2.注意左闭右开的区间。
3.本题中,吉利数字数量过多,可以统计不吉利数字然后减去。

题目链接

http://acm.hdu.edu.cn/showproblem.php?pid=2089

AC代码

#include<cstdio>
#include<iostream>
#include<cstring>

using namespace std;

int n, m;
int A[10];                                                              //把数字的每一位分开
int dp[10][3];              //dp[i][0] 前i位中不含不吉利数字的个数;dp[i][1] 不含不吉利且第i位是2;dp[i][2] 含不吉利

void init()                                                             //计算dp数组(无大小限制,每一位都能取0 ~9)
{                                                                       //特别的,从低位向高位算,与后面的统计顺序相反
    memset(dp, 0, sizeof dp);
    dp[0][0] = 1;
    for(int i= 1; i<= 6; i++)
    {
        dp[i][0] = dp[i - 1][0] * 9 - dp[i - 1][1];                     //吉利数前加上一个4以外的数都是吉利数,但是i-1位首位是2时不能加6
        dp[i][1] = dp[i - 1][0];                                        //吉利数前加上2构成吉利数且首位是2
        dp[i][2] = dp[i - 1][2] * 10 + dp[i - 1][0] + dp[i - 1][1];     //不吉利数前加任一数都不吉利,吉利数前加4不吉利,i-1位是2前加6也不吉利
    }
}

int solve(int x)                                                        //计算[0,x)中的吉利数字,注意左闭右开
{
    int left = x, len = 0;                                              //把x各位拆开,len~1高位到低位
    while(left)
    {
        A[++ len] = left % 10;
        left /= 10;
    }

    A[len + 1] = 0;                                                     //最高位的高一位看作0
    int res = 0;                                                        //实际上记录的是不吉利数的个数
    bool flag = false;                                                  //前n位是否已经不吉利
    for(int i= len; i>= 1; i --)                                        //枚举第i位小于A[i],i之前都等于A[k],i之后不受x限制
    {
        res += dp[i - 1][2] * A[i];                                     //i-1位不吉利,i位0~A[i-1]任选
        if(flag) res += dp[i - 1][0] * A[i];                            //前面的位已经不吉利,这一位随便取
        else
        {
            if(A[i] > 4) res += dp[i - 1][0];                           //i位能取到4,吉利数加4不吉利
            if(A[i] > 6) res += dp[i - 1][1];                           //i位能取到6,下一位是2的情况不吉利
            if(A[i + 1] == 6 && A[i] > 2) res += dp[i][1];              //高一位是6,这一位能取到2
        }
        if(A[i] == 4 || A[i + 1] == 6 && A[i] == 2) flag = true;        //前面的位已经不吉利
    }

    return x - res;                                                     //总数-不吉利数 = 吉利数
}

int main()
{
    init();

    while(1)
    {
        cin >> n >> m;
        if(n == 0 && m == 0) break;
        cout << solve(m + 1) - solve(n) << endl;
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值