强烈推荐:
数位dp总结 之 从入门到模板
对模板的理解:
/*
以题目“不要62”为例,其中输入区间右端点为 210。
那么处理该端点时,百位可取 0、1、2。
称取值方式以系列形式呈现,以下将取值可能分为 3 个系列,
其中系列 1、2 我称之为普通系列,系列 3 我称之为受限系列。
系列 1:百位为 0 时,十位可取 0-9,个位可取 0-9。
系列 2:百位为 1 时,十位可取 0-9,个位可取 0-9。
系列 3:百位为 2 时,十位仅可取 0、1。十位为 0 时,个位可取 0-9;十位为 1 时,个位仅可取 0。
可以理解两条注意点:
1. 系列 1 和系列 2 的满足条件的个数是一样的,加入系列 1 中满足条件要求的个数为 80 个,系列 2 中也应该为 80 个;
2. 系列 3 会有取值受限,取值限制由后文中的 limit 进行约束。
pos : 当前处理位上的数,比如十位中的 1
pre : 上一层的位上的数,比如百位中的 1
sta : 描述在每一位下,累加记忆的分类存储
limit : 用以判断取值是否受限,也就是当前处理的位能否随便取值。
*/
typedef long long LL;
// 记忆某一系列满足条件的个数,该系列是其中值不受限的普通系列。
int dp[pos][sta];
//储存每一位的最高位
int a[pos];
LL dfs(int pos,int pre,int sta,bool limit)
{
// 已搜索至尽头,返回 1 使计数器 + 1,前提是上一层已经将不满足条件的情况剔除。
if(pos == -1)
return 1;
// dp 记忆某一系列满足条件的个数,
// 如果系列 1 搜索过后得到其中满足条件要求的个数为 80 个。
// 那么系列 2 中满足条件要求的个数应该也为 80 个,在此处直接返回即可,无需再进行搜索。
if(!limit && dp[pos][sta] != -1)
return dp[pos][sta];
// 此处根据 limit 进行判断当前取值是否受限。
// 如果 limit 为 true,说明取值受限。
int up = limit ? a[pos] : 9;
LL ans = 0;
// 遍历每一个可取的值
for(int i=0;i<=up;i++)
{
// 此处根据题目要求将不满足题目条件的情况剔除
if(i==4) continue;
if(i==2&&pre==6) continue;
/*
pos-1 :表示开始处理下一位
i :表示以下一位的角度来看,当前位为前一位
i==6 :表示当前位取值到 6 时,要改变 dp 记忆的位置
limit&&i==a[pos] :表示根据当前位取到的数值来确定下一位是否受限
*/
ans += dfs(pos-1,i,i==6,limit&&i==a[pos]);
}
// 如果当前处理的系列不是受限系列,而是如同系列 1、2 的普通系列,可记忆下来当前满足条件的个数
if(!limit)
dp[pos][sta] = ans;
return ans;
}
LL solve(LL x)
{
int pos=0;
// a[0]=0 a[1]=1 a[2]=2
while(x)
{
a[pos++]=x%10;
x/=10;
}
// 当前相当于 dfs(2,-1,0,true)
// 表示当前处理位是百位,由于百位取值为 2、1、0,所以是受限的
return dfs(pos-1,-1,0,true);
}
int main()
{
memset(dp,-1,sizeof(dp));
LL a,b;
cin>>a>>b;
cout<<solve(b)-solve(a-1)<<endl;
return 0;
}
ps:待补充