夜深人静写算法(二十九)- 数位DP

Posted 英雄哪里出来

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了夜深人静写算法(二十九)- 数位DP相关的知识,希望对你有一定的参考价值。

一、前言

  数位 DP 又称 数位动态规划,在 LeetCode 上属于难题,而 ACM 竞赛中属于中等题,甚至可以说是模板题。
  数位 DP 的状态设计千变万化,但万变不离其宗,只要确定这个题是用数位 DP 求解,基本就很容易把状态套出来。当然,对于刚接触动态规划的同学,建议先看下 背包问题、最长单调子序列、最长公共子序列、记忆化搜索 等基础内容,对状态机和状态转移有一个初步的认识,那么,在学习数位 DP 时能够起到事半功倍的效果。
在这里插入图片描述

二、数位 DP 简介

1、数位 DP 定义

  • 数位 DP 又称 数位动态规划,一般可以从题干就能确定这个题是否可以用 数位 DP 来求解。因为 数位 DP 的题目一般都描述成如下两种问法之一:

【问法1】给定一个闭区间 [ l , r ] [l, r] [l,r],求在这个区间中,满足 某些条件 的数的个数。
【问法2】如果一个数字满足 某些条件,则称之为 X X X 数,给定闭区间 [ l , r ] [l, r] [l,r],求这个区间中 X X X 数的个数。

  • 这里的 某些条件 决定了状态转移的决策,这样说或许比较抽象,那么接下来,我们通过一个简单的例题来了解下 数位DP 的一般求解过程。

2、数位 DP 引例

【例题1】如果一个数的所有位数加起来是 10 10 10 的倍数, 则称之为 g o o d   n u m b e r good \\ number good number,求区间 [ l , r ] ( 0 ≤ l ≤ r ≤ 1 0 18 ) [l, r](0 \\le l \\le r \\le 10^{18}) [l,r](0lr1018) g o o d   n u m b e r good \\ number good number 的个数;

  • 对于这个问题,朴素算法就是枚举区间里的每个数,并且判断可行性,时间复杂度为 o ( ( r − l ) c ) o((r-l)c) o((rl)c) c = 19 c=19 c=19,肯定是无法接受的。

1)差分转换

  • 对于区间 [ l , r ] [l, r] [l,r] 内求满足数量的数,可以利用差分法分解问题;
  • 假设 [ 0 , x ] [0, x] [0,x] 内的 g o o d   n u m b e r good \\ number good number 数量为 g x g_x gx,那么区间 [ l , r ] [l, r] [l,r] 内的数量就是 g r − g l − 1 g_r - g_{l-1} grgl1;分别用同样的方法求出 g r g_r gr g l − 1 g_{l-1} gl1,再相减即可;
    在这里插入图片描述
    图二-2-1

2)数位性质

  • 如果一个数字 i i i 满足 i < x i < x i<x,那么 i i i 从高到低肯定出现某一位,使得这一位上的数值小于 x x x 对应位上的数值,并且之前的所有高位都和 x x x 上的位相等。
  • 举个例子,当 x = 1314 x = 1314 x=1314 时, i = 0 a b c i=0abc i=0abc i = 12 a b i=12ab i=12ab i = 130 a i=130a i=130a i = 1312 i=1312 i=1312,那么对于 i i i 而言,无论后面的字母取什么数字,都是满足 i < x i < x i<x 这个条件的。
  • 如图二-2-2所示:
    在这里插入图片描述
    图二-2-2
  • 如果我们要求 g 1314 g_{1314} g1314 的值,可以通过枚举高位:当最高位为0,那么问题就转化成 g 999 g_{999} g999 的子问题;当最高位为1,问题就转化成 g 314 g_{314} g314 的子问题。
  • g 314 g_{314} g314 可以继续递归求解,而 g 999 g_{999} g999 由于每一位数字范围都是 [ 0 , 9 ] [0,9] [0,9],可以转换成一般的动态规划问题来求解。

3)前缀状态

  • 这里的前缀状态就对应了之前提到的 某些条件
  • 在这个问题中,前缀状态的描述为:一个数字前缀的各位数字之和对10取余(模)的值。
  • 前缀状态的变化过程如图二-2-3所示:
    在这里插入图片描述
    图二-2-3
  • 在【例题1】中,前缀状态的含义是:对于一个数 520 ,我们不需要记录 520 ,而只需要记录 7;对于 52013,我们不需要记录 52013,而只需要记录 1。这样就把原本海量的状态,变成了最多 10 个状态。

3、状态分析

1)状态定义

  • 根据以上的三个信息,我们可以从高位到低位枚举数字 i i i 的每一位,逐步把问题转化成小问题求解。
  • 我们可以定义 f ( n , s t , l i m ) f(n, st, lim) f(n,st,lim) 表示剩下还需要考虑 n n n 位数字,前面已经枚举的高位组成的前缀状态为 s t st st,且用 l i m lim lim 表示当前第 n n n 位是否能够取到最大值(对于 b b b 进制,最大值就是 b − 1 b-1 b1,比如 10 进制状态下,最大值就是 9) 时的数字个数。我们来仔细解释一下这三维代表的含义:
  • 1)当前枚举的位是 n n n 位, n n n 大的代表高位,小的代表低位;
  • 2) s t st st 就是前缀状态,在这个问题中,代表了所有已经枚举的高位(即数字前缀)的各位数字之和对10取余(模)。注意:我们并不关心前缀的每一位数字是什么,而只关心它们加和模10之后的值是什么。
    图二-3-1
  • 3) l i m = t r u e lim=true lim=true 表示的是已经枚举的高位中已经出现了某一位比给定 x x x 对应位小的数,那么后面枚举的每个数最大值就不再受 x x x 控制;否则,最大值就应该是 x x x 的对应位。举例说明,当十进制下的数 x = 1314 x = 1314 x=1314 时,枚举到高位前三位为 “131”, l i m = f a l s e lim = false lim=false, 那么第四位数字的区间取值就是 [ 0 , 4 ] [0,4] [0,4];而枚举到高位前三位为 “130” 时, l i m = t r u e lim = true lim=true,那么第四位数字的区间取值就是 [ 0 , 9 ] [0, 9] [0,9]。参考 图二-2-2 加深理解。

2)状态转移

  • 所以,我们根据以上的状态,预处理 x x x 的每个数位,表示成十进制如下:
  • x = d n d n − 1 . . . d 1 x = d_nd_{n-1}...d_1 x=dndn1...d1
  • (其中 d n d_n dn 代表最高位, d 1 d_1 d1 代表最低位)
  • 得出状态转移方程如下:
  • f ( n , s t , l i m ) = ∑ k = 0 m a x v f ( n − 1 , ( s t + k ) m o d    10 , l i m   o r   ( k < m a x v ) ) \\begin{aligned}& f(n, st, lim) \\\\ &= \\sum_{k=0}^{maxv} f(n-1, (st+k) \\mod 10, lim \\ or \\ (k < maxv))\\end{aligned} f(n,st,lim)=k=0maxvf(n1,(st+k)mod10,lim or (以上是关于夜深人静写算法(二十九)- 数位DP的主要内容,如果未能解决你的问题,请参考以下文章

    ACM专题

    初探数位dp

    xsy1611 数位dp 数位dp

    [SCOI2009] Windy数 - 数位dp

    数位DP

    数位DP套路模板