第五章 动态规划(8):数位DP模型

目录

  • 1、计数问题
  • 2、度的数量
  • 3、数字游戏
  • 4、Windy数
  • 5、数字游戏 II
  • 6、不要62
  • 7、恨7不成妻

数位DP技巧:

  1. [X, Y] → f(Y) - f(X - 1)f(N)表示 1 ~ N 中满足某种性质的个数。比如第一题计数问题;
  2. 利用树的角度考虑,比如度的数量。

1、计数问题

ACWing 338

算法思路:一定要分情况讨论

首先,题目要求在[a, b]0 ~ 910个数中分别出现的次数,那么我们先实现一个函数count(n, x):求出从 1 ~ n 中 x 出现的次数,则在[a, b]x出现的次数表示为cout(b, x) - count(a-1, x)(类似于前缀和)。

count(n, x)函数,例如当x=1时,则count(n, 1)表示为从1 ~ n中找出1出现的次数。先求出11 ~ n中每一个位上出现的次数,则其总和即为count(n, 1)

例如有abcdefg,则1在第四位出现可以表示为1 <= xxx1yyy <= abcdefg,即为表示在1 ~ abcdefg之间有多少个数的形式是xxx1yyy。分情况讨论:

  • ① 若xxx = 000 ~ abc - 1时,xxx1yyy < abc1yyy <= abc1efg,则yyy 可取 000 ~ 999,总共有abc * 1000种选法;
  • ② 若xxx = abc时:
    • d < 1d = 0时,则第四位为1abc1yyy,但是abc1yyy > abc0efg,超出了范围,总共有0种选法;
    • d = 1时,即abc1yyy,则yyy 只能取 000 ~ efg,总共有efg + 1种选法;
    • d > 1时,则abcdefg > abc1yyy,故yyy可取000 ~ 999,共1000种选法。

由以上,依次类推,可以求出1在任意一位数上出现的次数,然后将每一个位上的1出现的次数累加起来,就能得到11 ~ n位中整个出现的次数。依次类推,可以求出1 ~ 9每一个数在1 ~ n中整个出现的次数。

边界情况:

  • 当枚举数字0的时候,对于②不存在前导零,不用考虑;对于①,只能从001开始枚举。
# include 
# include 
using namespace std;

int dgt(int n) // 计算整数n有多少位
{
   
    int res = 0;
    while (n) ++ res, n /= 10;
    return res;
}

int cnt(int n, int i) // 计算从1到n的整数中数字i出现多少次 
{
   
    int res = 0, d = dgt(n); // d是n的位数
    for (int j = 1; j <= d; ++ j) // 计算从右到左第j位上数字i出现多少次
    {
   
        // l和r是第j位左边和右边的整数 (视频中的abc和efg); dj是第j位的数字
        int p = pow(10, j - 1), l = n / p / 10, r = n % p, dj = n / p % 10;
        // 计算第j位左边的整数小于l (视频中xxx = 000 ~ abc - 1)的情况
        if (i) res += l * p; 
        // 如果i = 0, 左边高位不能全为0(视频中xxx = 001 ~ abc - 1)
        if (!i && l) res += (l - 1) * p; 
        // 计算第j位左边的整数等于l,不能存在前导零 (视频中xxx = abc)的情况
        if ( (dj == i) && (i || l) ) res += r + 1;
        if ( (dj > i) && (i || l) ) res += p;
    }
    return res;
}

int main()
{
   
    int a, b;
    while (cin >> a >> b , a)
    {
   
        if (a > b) swap(a, b);
        for (int i = 0; i <= 9; ++ i) cout << cnt(b, i) - cnt(a - 1, i) << ' ';
        cout << endl;
    }
    return 0;
}

2、度的数量

ACwing 1081

题中引例的意思实质上是:找到区间[x,y]中的某些数,这些数的B进制(引例是B=2二进制) 表示中仅包含k = 21,其余为0

假设我们需要求 [ 0 , N ] [0, N] [0,N]中满足某种性质的个数即数位DP技巧中的f[N],若数 N N N n n n位数,其表示为 a n − 1 、 a n − 2 、 . . . 、 a 0 a_{n-1}、a_{n-2} 、... 、a_0 an1an2...a0

  1. 一定要注意审题:这个数恰好等于 K 个互不相等的 B 的整数次幂之和。这句话的意思就表明了 K 个互不相等的 B 的整数次幂前面的系数只能是0 or 1!!!所以在下面图中,比如结点 0 0 0 ~ a n − 1 − 1 a_{n-1}-1 an11 仅有两个分支0 or 1,大于1的分支都不合法!
  2. 这里 N N N是已经转换为了 B B B进制下的数,比如: 19 19 19,在 3 3 3进制为 19 = ( 201 ) 3 19 =(201)_3 19=(201)3,即 19 = 2 × 3 2 + 0 × 3 1 + 1 × 3 0 19 = 2\times3^2+0\times3^1+1\times3^0 19=2×32+0×31+1×30

第五章 动态规划(8):数位DP模型_第1张图片

组合数的计算公式 C a b = C a − 1 b − 1 + C a − 1 b C_a^b=C_{a-1}^{b-1}+C_{a-1}^b Cab=Ca1b1+Ca1b。代码中使用f[a][b]来表示:从a中选择b个数的方案数。

#include 
#include 
#include 
#include 

using namespace std;

const int N = 35; // 1 ≤ X ≤ Y ≤ 2^31−1,所以最多31位

int K, B;
int f[N][N]; // f[a][b]:从a个数中选择b个数的方案数

void init() // 预处理f[a][b]

你可能感兴趣的:(算法笔记,动态规划,算法,c++)