数位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
解题思路:模拟计数走一遍流程就清楚了,详解看代码。
AC代码一:
 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int n,m,dp[10][10],d[10];
 4 void init(){
 5     memset(dp,0,sizeof(dp));
 6     dp[0][0]=1;///特殊定义0位数首位是0的方案数为1
 7     for(int i=1;i<10;++i){///表示i位数,最高位从1开始
 8         for(int j=0;j<10;++j){///表示i位数的首位数字是j--->(0~9)
 9             if(j==4)continue;///如果当前首位为4,dp[i][j]=0
10             ///累加i-1位数的首位是k的方案数,因为最高位已经确定,所以只需累加比其小的方案数即可
11             for(int k=0;k<10;++k){
12                 if(j==6&&k==2)continue;///当前首位j与其右边这一位上的数字k不能组成62
13                 dp[i][j]+=dp[i-1][k];///状态转移方程
14             }
15         }
16     }
17 }
18 int solve(int x){///累加小于x的所有数的方案数,直到遇到4或者是62就退出累加
19     memset(d,0,sizeof(d));///d数组记录x每一位上的数字
20     int len=0,ans=0;
21     while(x){///得到x的每一位数字
22         d[++len]=x%10;
23         x/=10;
24     }
25     ///d[len+1]=0;///前面已经置0,避免产生上一次的结果6对这个统计的影响
26     for(int i=len;i>=1;--i){///从高位向低位(从大到小)枚举位数i
27         for(int j=0;j<d[i];++j){///巧妙处理:每当进入到下一位,就默认上一位确定
28             if(d[i+1]==6&&j==2)continue;///跳过62,累加所有方案数
29                 ans+=dp[i][j];///dp[i][4]都为0,所以这里无需判断j==4
30         }
31         ///如果该位上是4或者(上一位是6并且当前位上是2)则直接退出后面的方案数的累加,因为后面的都不合法了
32         if(d[i]==4||(d[i+1]==6&&d[i]==2))break;
33     }
34     return ans;
35 }
36 int main(){
37     init();///预处理当前第i位首位是j符合条件的方案数
38     ///for(int i=0;i<10;++i)cout<<dp[i][4]<<endl;
39     while(~scanf("%d%d",&n,&m)&&(n|m)){
40         printf("%d
",solve(m+1)-solve(n));
41         ///差分思想,需要将[0,m+1)即[0,m]中的所有方案数减去[0,n)中的方案数才能得到[n,m]中满足条件的所有情况数
42     }
43     return 0;
44 }

AC代码二:记忆化搜索。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int n,m,dp[10][2],d[10];
 4 ///dp[pos][status]来保存在第pos位,status表示上一位是否为6这个状态
 5 int dfs(int pos,bool if6,bool limit){///limit用来判断前一位是否为数位上界,是则本位不能取到大于a[pos]的数
 6     if(!pos)return 1;///特殊情况下为1,即dp[0][0]=1;
 7     if(!limit&&dp[pos][if6]!=-1)return dp[pos][if6];///若前一位不是上限,并且dp[pos][if6]已确定,直接return统计,这里体现了记忆化搜索
 8     int up=limit?d[pos]:9,ans=0;///limit判断pos前的几位数字是否与n一样
 9     for(int i=0;i<=up;++i){
10         if(i==4||(if6&&i==2))continue;///这里is_6的值是0/1,用来区分前一位是否为6
11         ///cout<<"当前第"<<pos<<"位,上限为"<<limit<<",前一个数是否为6:"<<if6<<",当前位为"<<i<<endl;
12         ans+=dfs(pos-1,i==6,limit&&i==up);
13     }
14     return limit?ans:(dp[pos][if6]=ans);///若前一位不是上限,即这一位可以达到最大,则更新dp值
15 }
16 int solve(int x){
17     memset(d,0,sizeof(d));
18     int len=0;
19     while(x){d[++len]=x%10;x/=10;}
20     return dfs(len,false,true);///刚开始前0位是相同的
21 }
22 int main(){
23     memset(dp,-1,sizeof(dp));
24     while(~scanf("%d%d",&n,&m)&&(m|n)){
25         printf("%d
",solve(m)-solve(n-1));
26     }
27     return 0;
28 }
原文地址:https://www.cnblogs.com/acgoto/p/10047009.html