一、数位DP
用于解决 范围 [L, R] 内满足某些条件的数有多少 的问题,所谓数位,就是指数的个位、十位、百位、千位…
一般会将先将求ans [L, R] 转化为求 ans [0, R] - ans [0, L-1] ,从而问题就变为数位DP求解 ans [0, X]
数位DP最重要的是状态的构建,而具体DP的过程则是利用DFS+记忆化搜索实现,这样更好理解并实现,而状态的构建则更像是状态的压缩,将 [0, X]的状态根据题意进行压缩。
二、状态构建样例
① 不要62 HDU - 2089
该题要求数中不含’4’ 、不含连续的 '62’
不含’4’可以在DFS中直接限制,而不含连续的 ‘62’ 需要构建状态:dp[pos][pre_is_six]
因为对于 pos 之后的数位的状态,只有前一位为’6’(之后不可接’2’) 和 前一位不为’6’(之后可接’2’) 这两种状态。
② Round Numbers POJ - 3252
该题要求数的二进制形式0的数量不少于1的数量
首先要以二进制形式对X进行拆分,然后同样的进行数位DP
构建状态:dp[pos][cnt0][cnt1]
到达递归边界cnt0>=cnt1时,return 1; 否则 return 0;
同时要注意记录前导零,因为若当前0是前导零时,不能令cnt0+1
③ B-number HDU - 3652
该题要求数中含有连续的’13’,且数能被13整除
构建状态:dp[pos][pre_is_one][ok][[rem]
pre_is_one记录前一个位是否为1,ok记录是否已有连续的’13’,从而保证第一个条件;
而对于第二个条件,能被13整除:
首先我们知道在DFS过程中,产生一个数是通过不断的 num = num*10 + i 得到的,最后再判断 num%13是否为0,但是num会很大,状态太多无法记录。
而由求模运算的性质可以知道,我们可以每次对num进行求模,而最后的结果并不会改变,那么就可以用rem每次记录num%13的状态,那么就变成了每次 rem = ( rem*10 + i ) % 13
最后到达边界且 ok&&rem==0时,return 1; 否则 return 0;
含有/不含有某些数,含由多少个某些数,能够被某个数整除…除这些简单的以外还有着很多比较复杂的状态构建,数位DP的核心思想还是不会变的。
三、模板
#include<bits/stdc++.h>
#define LL long long
using namespace std