数位dp知识点整理
题解报告:hdu 2089 不要62
Problem Description
杭州人称那些傻乎乎粘嗒嗒的人为62(音:laoer)。
杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除个别的士司机和乘客的心理障碍,更安全地服务大众。
不吉利的数字为所有含有4或62的号码。例如:
62315 73418 88914
都属于不吉利号码。但是,61152虽然含有6和2,但不是62连号,所以不属于不吉利数字之列。
你的任务是,对于每次给出的一个牌照区间号,推断出交管局今次又要实际上给多少辆新的士车上牌照了。
杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除个别的士司机和乘客的心理障碍,更安全地服务大众。
不吉利的数字为所有含有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\n",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\n",solve(m)-solve(n-1)); 26 } 27 return 0; 28 }