题目链接:https://vjudge.net/contest/381753#problem/G

知识预备:数位DP
数位DP的题目往往是这样的:
给定一个闭区间[L,R],求这个区间中满足"某种条件"的数的总数量。
数位DP技巧:
技巧1:[X,Y] -> f(Y)-f(X-1)
把统计[L,R]范围满足条件的数的个数转化为统计[1,N]内满足条件的数的个数,这样,f([L,R]) = f([1,R])-f([1,L-1]) , 那么接下来讨论问题只需要考虑上边界就行了。
技巧2:树的方式来考虑
图片说明
代码实现用dfs从高位到低位枚举
解题思路:
对于这道题,dfs需要记录的状态有:
1.现在枚举第几位
2.前面一位的数字是多少
3.这一位可以填哪些数字

位置pos有两种可能性:
1.如果pos前面某一位已经小于上限数字所对应位置的数字,那么这一位可以填0-9
2.如果pos前面每一位都与上限数字所对应位置的数字相同,那么这一位只能填0-图片说明
因此,维护前面每一位是否与上限所对应位置的数字相同,就可以得到pos位枚举数字范围。
然后用一个dp[pos][pre_num]来记录,避免重复调用。
这里需要注意,!flag的情况下和flag情况下;有前导0和无前导0情况下,dp[pos][pre_num]是不同的。

代码:

#include<bits/stdc++.h>
using namespace std;
int a[30];
int dp[30][30];
int dfs(int pos, int pre, bool flag, bool lead)
{
    //lead用于判断从高位到低位枚举时有没有前导0,
    //如对1232
    //当前面几位枚举的都是0时,后面一位的枚举是不受前面一位限制的,
    //因为如果前面几位都是0,就相当于前几位是不存在的,
    //所以才有: if( abs(i-pre) >=2 || lead)
    if(!pos) return 1;
    if(dp[pos][pre] != -1&&!flag&&!lead) return dp[pos][pre];
    int ans = 0, max_num;
    if(flag) max_num = a[pos]; else max_num = 9;
    for(int i = 0; i <= max_num; i++) {
        if(abs(i-pre) >= 2 || lead) {
            ans+=dfs(pos-1, i, flag&&(i==a[pos]), lead&&(i==0));
        }
    }
    if(!flag&&!lead) dp[pos][pre] = ans;
    return ans;
}
int solve(int x)
{
    int len = 0;
    while(x) a[++len]=x%10, x/=10;
    memset(dp, -1, sizeof(dp));
    return dfs(len, -2, 1, 1);
}
int main()
{
    int l, r;
    cin >> l >> r;
    cout << solve(r)-solve(l-1) << endl;
}