数位dp是以数位上的关系为转移关系而进行的一种计数dp,题目基本类型是给定区间[l ,r] ,求l到r之间满足要求的数字的个数 .
dp状态的转移方式通常是用 递归+记忆化搜索 ,转移顺序一般是由高数位转移到底数位 ,其中就是记忆化搜索保证了数位dp的高效率
例如千位2到百位转移要枚举0,1,2,3 ...(2000,2100,2200,2300...) ,而千位3也是同样的(3000,3100,3200,3300...),其进行的都是对三位数000~999的统计,所以低位统计过程只用进行一次就可将结果应用于所有高位状态上,减少了重复过程的进行.
结果的输出形式是 0~r 之间的dp 与 0~l之间的dp 进行相减 来求 l到r 之间的 dp.
printf("%lld\n",solve(r)-solve(l-1));
值得注意的点是边界 l和r 不能进行记忆化搜索 ,比如 dp[2][sta] 记录的是 000~999(三位数) 中满足条件的数字的个数 ,而对于l = 2250 ,其在2000之后的三位数只有 100~250 ,所以这时候如果直接记忆化返回 dp[2][sta] 就会出现多记.
有的题目对前导零有要求,有的没有,做的时候随机应变。
例题:
HDU 2089 不要62
题解:转移状态很清晰明了的题目,主要通过此题了解 递归+记忆化的转移方式,对题目要求如何在dfs函数中进行处理 ,lim边界标记的使用和传递的方式.
#include
#include
#include
#include
#include <set>
#include
#include
#include <string>
#include
#include
#include
常用优化:
1.就算面对不同询问,数位的dp状态也往往是相同的,因此在约束条件普适于所有数字的条件下不用每次都用mem对dp进行初始化
如果面对不同询问,在约束条件下产生的状态不普适的条件下(如 一个数是它自己数位和的倍数),可以将dp增加一维dp[pos][state][limit],或者直接每次都初始化dp
2.状态的表示方法不同也会影响dp的适用范围,一种常见的状态表示是将和式的状态表示为 当前数位和 与 目标值 所需要拼凑的差值
例如:HDU 4734
F(x) = An * 2n-1 + An-1 * 2n-2 + ... + A2 * 2 + A1 * 1,Ai是十进制数位,给出a,b求区间[0,b]内满足f(i)<=f(a)的i的个数。
如果正面思考,用数位和sum作为状态,逐位拼凑到a,则状态dp[pos][sum]无法复用,因为随着a的变化,sum
不如反面思考,将初始与目标值的差值作为状态,初始状态为f(a),逐位相减,如果在dp转移到最后一位差值仍大于等于零则说明f(i)<=f(a),对于不同的a,如果减到某一位后他们的状态:与目标值的差值相同,则接下来的位数中满足相减完毕后结果大于等于0的方案数也相同 ,dp是可以复用的
#include
#include
#include
#include
#include <set>
#include
#include
#include <string>
#include
#include
#include
其他题目:
HDU 3709 这题就是要枚举中轴,然后数位dp
#include
#include
#include
#include
#include <set>
#include
#include
#include <string>
#include
#include
#include
这题需要注意的是在枚举中轴的过程中,对于数字0,无论中轴在哪一位都能满足,因此最后要减去0的重复计数
HYSBZ - 1799 给出a,b,求出[a,b]中各位数字之和能整除原数的数的个数。
数位和相较于原来的a,b较小,最大只有9*18 == 162 ,也就是说在a~b之间有很多数的数位和是相同的,可以直接枚举除数mod
状态 dp[pos][val][mod],其中val是枚举到某一位的余数
#include
#include
#include
#include
#include <set>
#include
#include
#include <string>
#include
#include
#include
进阶:dp所求不是a~b之间数字个数的情况,例如求a~b之间满足条件数字的和,求a~b之间满足条件数字的平方和
处理方法是在转移过程中考虑各位为dp结果产生的贡献,写出dp的转移方程
和 : sum[pos] = sum[pos-1][0~9] + cnt[pos-1][0~9]*i*10^pos
平方和:i为当前位上的数 ,假设b为低位上余下的数值,则将表达式展开(i*10^pos + b)^2 = (i*10^pos)^2 + 2*b*i*10^pos + b^2
故转移方程 :
sum_sq[pos] = cnt[pos-1][0~9]*(i*10^pos)^2 + 2*i*10^pos * sum[pos-1][0~9] + sum_sq[pos-1][0~9];
sum[pos] = sum[pos-1][0~9] + cnt[pos-1][0~9]*i*10^pos
例题:HDU 4507
简单的约束条件,但所求结果不是计数而是平方和
#include
#include
#include
#include
#include <set>
#include
#include
#include <string>
#include
#include
#include
方法就是考虑每一位的贡献,转移方程就是上面推的
复制下别人的题解:
关于数字7的限制其实不难实现,难的是要求平方和,考虑2XX这个数,也就是百位为2的数,它满足限制条件的平方和是多少?
假设满足条件的数有234,245,266,那么 234^2 + 245^2 + 266^2 = (200 + 34)^2 + (200 + 45)^2 + (200 + 66)^2
= 3*200^2 + 2*200*(34+45+66) + (34^2 + 35^2 + 66^2),因此在枚举到2的时候,表达式里只有3,(34 + 45 + 66),(34^2 + 35^2 + 66^2)不知道,
因此我们可以定义dfs1(i)为求平方和,dfs2(i)为求和,dfs3(i)为求有多少个满足条件的数(也就是上述的3)。
另外注意算的时候每一步都取模,不要爆longlong了