HDU 2089 不要62(数位DP)

题目:

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

Inpu

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


Output

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

 

Sample Input

1 100
0 0

Sample Output

80

题意:给出2个数n,m 且 n<=m,问区间【n,m】中有多少个不包含4和不包含连续的62的数。
暴力枚举就超时了,下面介绍一下数位DP

数位DP也就是一个记忆化搜索的过程,其实也就是模拟数数的过程(深搜)
举个栗子,我们正常从1到5123数数
就相当于数0001,0002,.....0010,0011,0012......1000....5123这样一个一个数
而数位DP,就是按个位,十位,百位,按位来数
思想是这样的,假设
n=1,m=5123

上面划线的数字组合起来就表示5123

我们定义,dp【20】【2】

     digit【20】//把数字的每一个数位存起来,对5123来说,digit【1】=3,digit【2】=2,digit【3】=1,digit【4】=5,

解释:dp【i】【1】表示当数位i+1的数字是6时候,数位i有多少个不包含4和不包含62的数

      dp【i】【0】表示当数位i+1的数字不是6时候,数位i有多少个不包含4和不包含62的数

比如dp【1】【0】= 9(当数位2,即十位的数字不是6时,个位可以提供的数字有0,1,2,3,5,6,7,8,9共9个)

  dp【1】【1】= 8(当数位2,即十位的数字是6是,个位可以提供的数字有0,1,3,5,6,7,8,9共8个)

 

我们先跑一遍位数是1的数,更新dp【1】【1】和dp【1】【0】

当我们跑到十位0时,因为这时候的数字是0,不是6,所以十位数是0的贡献是dp【1】【0】=9

同理,当十位是1,2,3,5,7,8,9是,贡献都是dp【1】【0】=9

十位是4的时候,因为不能出现数字4,所以它的贡献是0

十位是6的时候,贡献是dp【1】【1】=8(60,61,63,65,66,67,68,69共8个)

更新一下dp【2】【0】

它的值就应该是十位数字0,1,2,3,5,6,7,8,9贡献之和,即9*8+8=80

再更新一下dp【2】【1】

它的值就应该是十位数字0,1,3,5,6,7,8,9贡献之和,即9*7+8=71

这样就可以一直更新上去

把各个数位上的数字贡献记录下来,提供给更高位的数位,这样就实现了记忆化搜索

当然还有边界问题

当我们千位是5的时候,千位(最高位)的数字到了最大值,百位就只能看0和1的贡献

百位数字为0的贡献可以直接利用,因为5000-5099都在1-5125的范围内

但1是百位的最大值(在千位是最高位的前提下),不能直接利用百位是1的贡献(5100-5199有部分数超过了5123)

所以十位是0,1的贡献可以直接利用,十位是2的贡献不能直接利用

只需要特判一下就可以了

这个过程还是听视频讲解的好(画图要画好多张啊,偷懒偷懒)

推荐一个视频  https://www.bilibili.com/video/av27156563?t=2383

博主也是看了这位大佬的视频才理解了数位dp(当然下面的代码也是这位大佬的)

代码实现:(详细看注释)

ll dfs(int len,bool if6,bool limit)
{/*len表示当前的数位,if6表示上一个数位的数字是不是6,limit表示比现在数位高的数位是不是都到了最大值,即限制值,如千位是5,百位是1,那么十位就只能取0,1,2,起到了限制作用*/
    if(len==0) return 1;//递归边界
    if(!limit && dp[len][if6]) return dp[len][if6];
/*提速部分,如果没有被限制而且dp数组已经更新,则直接使用dp数组*/
    ll cnt=0,up_bound=(limit?digit[len]:9);
/*cnt用来计数,up_bound表示上界,如果高位没有被限制,则该位可以是数字0到9,否则该位数字是0到digit【len】,对于十位来说就是0到2(假如千位是5,百位是1,高位限制了)*/
    for(int i=0;i<=up_bound;++i){
        if(if6 && i==2) continue;//上一位数字是6而且当前位数数字是2,忽略
        if(i==4) continue;//当前数位数字是4,忽略
        cnt+=dfs(len-1,i==6,limit&&i==up_bound);
/*下一个数位的各个数字贡献之和就是当前位数数字是i的贡献,第三个参数表示当前数位和高数位是不是都达到了限制值*/
    }
    if(!limit) dp[len][if6]=cnt;
/*没有被限制的数位计算出的cnt才能代表整个数位的贡献,否则会有残缺*/
    return cnt;
}
ll solve(ll num)
{
    int k=0;
    while(num)//分解num各个数位上的数字
    {
        digit[++k]=num%10;
        num/=10;
    }
    return dfs(k,0,1);
/*从高位开始向低位搜索,最高的数自然是自己本身,它的上一位只能是0,不是6,所以第二个参数写0,因为没有15123,只有05123,所以第三个参数写1*/
}
View Code

AC代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long int ll;
int digit[20];
ll dp[20][2];
ll dfs(int len,bool if6,bool limit)
{
    if(len==0) return 1;
    if(!limit && dp[len][if6]) return dp[len][if6];
    ll cnt=0,up_bound=(limit?digit[len]:9);//上界;
    for(int i=0;i<=up_bound;++i){
        if(if6 && i==2) continue;
        if(i==4) continue;
        cnt+=dfs(len-1,i==6,limit&&i==up_bound);
    }
    if(!limit) dp[len][if6]=cnt;
    return cnt;
}
ll solve(ll num)
{
    int k=0;
    while(num)
    {
        digit[++k]=num%10;
        num/=10;
    }
    return dfs(k,0,1);
}
int main()
{
    ll n,m;
    while(cin>>n>>m)
    {
        memset(dp,0,sizeof(dp));
        if(n==0&&m==0) break;
        cout<<solve(m)-solve(n-1)<<'\n';
    }
    return 0;
}
View Code
 
posted @ 2019-05-16 00:30  丿不落良辰  阅读(192)  评论(0编辑  收藏  举报