数位DP入门 —— hdu2089 不要62

数位DP是一种用来计数的DP, 如果给你一道题, 让你去统计区间[l, r]之间满足某种条件的数字个数, 在没接触数位之前很容易想到的就是暴力判断。但当数据范围较大时这种方法就不可行了, 这时候我们就可能要用的差分的思想确定一个递推关系, 来更方便更高效的求解, 这就是数位DP。这种理论的东西可能写出来帮助不大, 数位DP的思想是通过自己对问题的思考逐渐建立的, 下面是我选的一个例题来帮助初步理解数位DP。

HDU 2089 —— 不要62

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

 

 

直接统计对于暴力枚举很好求,但是对于数位dp并不容易,所以我们还需要用到差分的思想,即统计0到b+1(注意不是b,至于为什么后面会讲)和0到a的满足条件的个数,再两者相减

进一步化简,求0到i位数不含4和62的个数

i=1,求0~9的满足条件的个数

i=2,求0~99的满足条件的个数

i=3,求0~999的满足条件的个数

i=4,求0~9999的满足条件的个数

...

用dp[i][0]表示i位数中幸运数的个数

用dp[i][1]表示i位数中以2开头的幸运数的个数

用dp[i][2]表示i位数中非幸运数的个数

那么,就有以下的递推公式

dp[i][0]=dp[i-1][0]*9-dp[i-1][1]//表示前i-1位数字中的幸运数前面加上除4以外的0~9的其他数字,共9个,还要减去前i-1位数字中的以2开头的幸运数加上这一位以6开头的数字的个数

dp[i][1]=dp[i-1][0]//表示前i-1位数字中的幸运数加上这一位的2

dp[i][2]=dp[i-1][2]*10+dp[i-1][1]+dp[i-1][0]//表示前面已经不合法的数字这一位无论放什么都不合法,所以0~9随便放,加上前i-1位数字中的以2开头的幸运数加上这一位的6,再加上前i-1位数字中的幸运数加上这一位的4的个数

初始值 dp[0][0]=1,其他均为0

根据初始值和递推公式,我们就能得到从0到任意i位数字的吉利数字的个数。

找到0 ~ n 的吉利数字的个数

我们先求出0 ~ n 之间非吉利数字的个数,用总数减去即可。那,非吉利数字的个数怎么求呢?

用具体的数字举例来说吧:设 n = 583626

用digit[10]记录n+1每一位对应的数字,此例中有6位数字(令cnt = 6 表示数字位数),分别是

digit[6] = 5

digit[5] = 8

digit[4] = 3

digit[3] = 6

digit[2] = 2

digit[1] = 7

digit[0] = 任意数字,占位用的

用sum记录非吉利数字的个数,初始化为0

需要一个bool量 flag,记录是否出现了非吉利数字。初始化为false, 未出现。

我们从数字的最高位起进行判断:digit[6] = 5, 我们求 0 ~ 499999 之间非吉利数的个数。

  首先:加上0 ~ 99999中所有非吉利数字前面添加0~4的任意一个数字的情况 sum += dp[5][2] * digit[6]

  其次:5大于4,故我们要加上 0~99999中所有吉利数字前面添加4的情况 sum += dp[5][0]

接着,判断第5位digit[5] = 8,即判断500000 ~ 579999 之间的非吉利数字的个数,其实就是判断0 ~ 79999之间的,前面的数字不是6就没有什么用

  首先:加上0 ~ 9999中所有非吉利数字前面添加0~7的任意一个数字的情况 sum += dp[4][2] * digit[5]

  其次:8大于4,故我们要加上 0~9999中所有吉利数字前面添加4的情况 sum += dp[4][0]

  此外:8大于6,故我们要加上0~9999中所有以2开头的吉利数字前添加6的情况 sum += dp[4][1]

接着,判断第4位digit[4] = 3,即判断580000 ~ 582999 之间的非吉利数字的个数,其实就是判断0 ~ 2999之间的

  首先:加上0 ~ 999中所有非吉利数字前面添加0~2的任意一个数字的情况 sum += dp[3][2] * digit[4]

  其次:2小于4,没有需要特别考虑的

  此外:2小于6,没有需要特别考虑的

接着,判断第3位digit[3] = 6,即判断583000 ~ 583599 之间的非吉利数字的个数,其实就是判断0 ~ 599之间的

  首先:加上0 ~ 99中所有非吉利数字前面添加0~5的任意一个数字的情况 sum += dp[2][2] * digit[3]

  其次:6大于4,故我们要加上 0~99中所有吉利数字前面添加4的情况 sum += dp[2][0]

接着,判断第2位digit[2] = 2,即判断583600 ~ 583619 之间的非吉利数字的个数,其实就是判断0 ~ 19之间的,

  首先:加上0 ~ 9中所有非吉利数字前面添加0~1的任意一个数字的情况 sum += dp[1][2] * digit[2]

  其次:2小于4,没有需要特别考虑的

  此外:2小于6,没有需要特别考虑的

  但是,需要注意的是,这里判断的数字出现了62,我们要把flag标识为true。

最后,判断第1位digit[1] = 7, 判断583620 ~ 583626但是这里flag为true了,表示前面的数字里面已经包含了非吉利数字,所以后面需要把所有的数字情况都加入到非吉利里面。(正是因为每次判断的数字末尾都比该位的数字少1,所以最开始要记录n + 1 的值)

sum += digit[1] * dp[0][2] + digit[1] * dp[0][0]

1 #include<bits/stdc++.h>
 2 #define in(i) (i=read())
 3 using namespace std;
 4 int read() {
 5   int ans=0,f=1; char i=getchar();
 6   while(i<'0'||i>'9') {if(i=='-') f=-1; i=getchar();}
 7   while(i>='0'&&i<='9') {ans=(ans<<3)+(ans<<1)+i-'0'; i=getchar();}
 8   return ans*f;
 9 }
10 int dp[10][3],digit[15];
11 void init() {
12   memset(dp,0,sizeof(dp));
13   dp[0][0]=1;
14   for(int i=1;i<=8;i++) {
15     dp[i][0]=dp[i-1][0]*9-dp[i-1][1];
16     dp[i][1]=dp[i-1][0];
17     dp[i][2]=dp[i-1][2]*10+dp[i-1][1]+dp[i-1][0];
18   }
19 }
20 int solve(int x)
21 {
22   memset(digit,0,sizeof(digit));
23   int cnt=0,tmp=x;
24   while(tmp) {
25     digit[++cnt]=tmp%10;
26     tmp/=10;
27   }
28   digit[cnt+1]=0; int flag=0,ans=0;
29   for(int i=cnt;i>=1;i--) {
30     ans+=digit[i]*dp[i-1][2];
31     if(flag) ans+=digit[i]*dp[i-1][0];
32     else {
33       if(digit[i]>4) ans+=dp[i-1][0];
34       if(digit[i]>6) ans+=dp[i-1][1];
35       if(digit[i+1]==6 && digit[i]>2) ans+=dp[i][1];
36     }
37     if(digit[i]==4 || (digit[i+1]==6 && digit[i]==2)) flag=1;
38   }
39   return x-ans;
40 }
41 int main()
42 {
43   int a,b;
44   init();
45   while(1) {
46     in(a);in(b);
47     if(!a && !b)  break;
48     cout<<solve(b+1)-solve(a)<<endl;
49   }
50  return 0;
51 }

最后说那个b+1的情况,我们看到代码中有判断digit[i]>4和digit[i]>6等类似的语句,我们处理第i位时,实际上是处理0~digit[i]-1,即[ 0,digit[i] ),而把digit[i]放到下一次去判断,但我们处理个位时,最后一个是不会去统计的,所以我们把统计的范围+1,即为[ 0,digit[i]+1 )-->[ 0,digit[i] ],所以就有了solve(b+1)-solve(a)这样的语句

 

 

 

 

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值