描述
题解
以1-100为例,函数递归调用过程如下图:
需要注意的两点是:
1)为什么需要将dp设置成dp[20][2]
这样大小的数组。(需要区分上一个数位是否取值6这两种情况)
以上图为例:
假设将数组dp设置成dp[20],
当百位为0,十位为0时,毋庸置疑,dp[0]为9。
当百位为0,十位为6时,由于dp[0]状态已有了,此时程序直接返回dp[0],显然这是错误的。
所以我们加上第二维,来标志上一位是否为6。为6,将第二维置为1;不为6。将第二维置为0。
2)记忆化搜索时,为什么需要加上 if(!limit)
这个判断条件
同样以上图为例
当百位为0时,毋庸置疑,dp[1][0]为79,
当百位为1时,此时limit为true,如若没有(!limit)
这个条件,由于dp[1][0]这个状态已经有了,程序直接返回dp[1][0],显然这是错误的。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
using namespace std;
typedef long long ll;
int a[20];
int dp[20][2];
/**
dfs(pos-1,-1,0,true)
pos-1:当前所枚举的数位
pre:前一个数位
sta:表示当前数位的前一位是否为6;为6还是不为6 这两种情况需要分开来登记
limit:当前所枚举的pos-1是否是最高位
**/
int dfs(int pos,int pre,int sta,bool limit)
{
if(pos==-1) return 1;
///记忆化搜索,(需要保证limit取值为false,也就是当前取值不受到数字大小的限制)
if(!limit && dp[pos][sta]!=-1) return dp[pos][sta];
///根据limit的取值获取下一位的取值上限
int up=limit ? a[pos] : 9;
int tmp=0;
for(int i=0;i<=up;i++)
{
///和sta结合起来考虑,如果sta取值为1,则这个条件必然会有触发的时候
if(pre==6 && i==2)continue;
if(i==4) continue;//都是保证枚举合法性
/**
下面参数第四项为limit && i==a[pos],而不是!limit
因为如果枚举最高位最大一个数字,
那么下一位的枚举上界也会受到限制
*/
tmp+=dfs(pos-1,i,i==6,limit && i==a[pos]);
}
if(!limit) dp[pos][sta]=tmp;
return tmp;
}
int solve(int x)
{
///获取最高位存储在pos中,并将每一位的数字存储在数组a中
int pos=0;
while(x)
{
a[pos++]=x%10;
x/=10;
}
return dfs(pos-1,-1,0,true);
}
int main()
{
int le,ri;
//memset(dp,-1,sizeof dp);可优化
while(~scanf("%d%d",&le,&ri) && le+ri)
{
memset(dp,-1,sizeof dp);
printf("%d\n",solve(ri)-solve(le-1));
}
return 0;
}