数位dp模板

模板

应用:求某个区间[l, r]中,满足某个条件的数的个数
思路:

  1. 函数f(x)求[0, x]合法的数的个数,答案为f(r) - f(l-1)
  2. 从高位向低位枚举,每一位t可以划分为两个分支,左分支为0,1,…,t-1,右分支为t
  3. 预处理左分支的所有合法个数,递归处理右分支
  4. 不重不漏动态规划[0, x]所有数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    #include <iostream>
    #include <cstring>
    #include <vector>
    #include <cmath>

    using namespace std;

    typedef long long ll;

    const ll N = 30;

    // f[i][j]:用于左分支
    ll f[N][N];

    // 预处理f
    void init(){}

    ll dp(ll n)
    {
    // 边界特判
    if (n == 0) return ;

    // 划分出每一位
    vector<ll> nums;
    while (n) nums.push_back(n % 10), n /= 10;

    // res:答案 last:与已经枚举完的数的相关信息
    ll res = 0, last = -2;
    // 从高位到低位枚举
    for (int i = nums.size() - 1; i >= 0; i--)
    {
    // 取出每一位数
    ll x = nums[i];
    // 左分支
    for (ll j = 0; j <= x - 1; j++)
    if ()
    res += f[][];

    // 判断是否合法
    if () // 更新last
    else break; //接下来右分支的所有数都不合法

    // 最右下角分支
    if (i == 0)
    res++;
    }

    // 特殊处理

    return res;
    }

    int main()
    {
    init();
    ll l, r;
    scanf("%lld%lld", &l, &r);
    //[0, r]合法数 - [0, l - 1]合法数
    printf("%lld\n", dp(r) - dp(l - 1));

    return 0;
    }

题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <iostream>
#include <cstring>
#include <vector>
#include <cmath>

using namespace std;

typedef long long ll;

const ll N = 15;

//f[i][j]:总共有i位,最高位是j的合法个数
ll f[N][N];

//初始化
void init()
{
for (int i = 0; i <= 9; i ++ ) f[1][i] = 1;
for (int i = 2; i < N; i ++ )
for (int j = 0; j <= 9; j ++ )
for (int k = 0; k <= 9; k ++ )
if (abs(j - k) >= 2)
f[i][j] += f[i - 1][k];
}

ll dp(ll n)
{
if (n == 0) return 0;

vector<ll> nums;
while (n) nums.push_back(n % 10), n /= 10;

//last:上一位数字
ll res = 0, last = -2;
for (int i = nums.size() - 1; i >= 0; i -- )
{
ll x = nums[i];
//左分支
for (ll j = i == nums.size() - 1; j <= x - 1; j ++ )
if (abs(last - j) >= 2)
res += f[i + 1][j];

//判断是否合法
if (abs(last - x) >= 2) last = x;
else break;

if (i == 0) res ++ ;
}

// 特殊处理有前导零的数
for (int i = 1; i < nums.size(); i ++ )
for (int j = 1; j <= 9; j ++ )
res += f[i][j];

return res;
}

int main()
{
init();
ll l, r;
scanf("%lld%lld", &l, &r);
printf("%lld\n", dp(r) - dp(l - 1));

return 0;
}