参考:https://blog.csdn.net/jk211766/article/details/81474632
数位dp
什么是数位dp
数位dp是一种计数用的dp,一般就是要统计一个区间[le,ri]内满足一些条件数的个数。所谓数位dp,字面意思就是在数位上进行dp咯。数位还算是比较好听的名字,数位的含义:一个数有个位、十位、百位、千位…数的每一位就是数位啦!
数位dp的实质就是换一种暴力枚举的方式,使得新的枚举方式满足dp的性质,然后记忆化就可以了。
最简单的暴力枚举法:
for i in range(arr):
if i == XXX:
ans++
显然使用这种方法在数据量非常大的时候肯定会超时。
数位dp分析
新的枚举:控制上界枚举,从最高位开始往下枚举,例如:ri=213,那么我们从百位开始枚举:百位可能的情况有0,1,2(觉得这里枚举0有问题的继续看)
然后每一位枚举都不能让枚举的这个数超过上界213(下界就是0或者1,这个次要),当百位枚举了1,那么十位枚举就是从0到9,因为百位1已经比上界2小了,后面数位枚举什么都不可能超过上界。所以问题就在于:当高位枚举刚好达到上界时,那么紧接着的一位枚举就有上界限制了。具体的这里如果百位枚举了2,那么十位的枚举情况就是0到1,如果前两位枚举了21,最后一位之是0到3(这一点正好对于代码模板里的一个变量limit 专门用来判断枚举范围)。最后一个问题:最高位枚举0:百位枚举0,相当于此时我枚举的这个数最多是两位数,如果十位继续枚举0,那么我枚举的就是一位数,因为我们要枚举的是小于等于ri的所有数,当然不能少了位数比ri小的咯!(这样枚举是为了无遗漏的枚举,不过可能会带来一个问题,就是前导零的问题,模板里用lead变量表示,不过这个不是每个题目都是会有影响的,可能前导零不会影响我们计数,具体要看题目)
我们的计算范围是 [ l e , r i ] [le,ri] [le,ri], 在算法中我们可以这样表示:
solve(ri)-solve(le-1)
数位dp的模板
d p [ i ] [ j ] : = 数 位 为 i , 状 态 为 j 时 满 足 条 件 的 个 数 。 dp[i][j] := 数位为i,状态为j时满足条件的个数。 dp[i][j]:=数位为i,状态为j时满足条件的个数。
基本的动态模板:
a = [0]
dp = [[0]*state for i in range(20)]
def solve(x):
global a
x = list(str(x))
x =x.reverse()
a = x
retrun dfs(len(a)-1,true,true)
def dfs(pos, lead, limit):
#limit 专门用来判断枚举范围; lead前导零;pos数的第几位
if pos == -1 :return 1
# 从最高位循环到最低位,返回1表示枚举的这个数合法的,不过返回值根据具体情况而不同
if not limit and not lead and dp[pos][state]!=-1:
return dp[pos][state]
if limit:
up = a[pos]
else:
up = 9
ans = 0
for i in range(up+1):
if :
pass
elif:
pass
lead = lead and i ==1
limit = limit and i = a[pos]
ans += dfs(pos-1,lead ,limit)
if not limit and not lead:
dp[pos][state]=ans
return ans
实例
不要62
杭州人称那些傻乎乎粘嗒嗒的人为62(音:laoer)。
杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除个别的士司机和乘客的心理障碍,更安全地服务大众。
不吉利的数字为所有含有4或62的号码。例如:62315 73418 88914
都属于不吉利号码。但是,61152虽然含有6和2,但不是62连号,所以不属于不吉利数字之列。
你的任务是,对于每次给出的一个牌照区间号,推断出交管局今次又要实际上给多少辆新的士车上牌照了。
分析:
该问题总结起来就是数位上不能有4和62
- 没有4:不枚举4,所以不需要 记忆化搜索
- 没有62:分为两种情况:当前一位是6和不是6。所以要记录不同状态。
令: d p [ p o s ] [ s t a t e ] : = 当 前 第 p o s 位 , 前 一 位 是 否 为 6 , 所 以 s t a t e 位 只 需 要 0 , 1 两 种 状 态 。 dp[pos][state]:= 当前第pos位,前一位是否为6,所以state位只需要0,1两种状态。 dp[pos][state]:=当前第pos位,前一位是否为6,所以state位只需要0,1两种状态。
dp =[]
a= []
def dfs(pos,pre,sta,limit): # 当前一位是6,sta=1,否则=0
if pos == -1:
return 1
if not limit and dp[pos][sta] != -1:
return dp[pos][sta]
up = a[pos] if limit else 9
tmp = 0
for i in range(up+1):
if pre == 6 and i ==2:
continue
if i ==4:
continue
tmp += dfs(pos-1,i,i==6,limit and i==a[pos])
if not limit:
dp[pos][sta] = tmp
return tmp
def solve(x):
global a
x = list(str(x))
x= [int(i) for i in x]
x.reverse()
a = x
pos = len(a)
return dfs(pos-1,-1,0,True)
if __name__ == "__main__":
le,ri=[int(i) for i in input().split()]
dp = [[-1]*2 for i in range(len(str(ri)))]
print(solve(ri)-solve(le-1))
print(dp)
```