20250922 - 数位dp 总结

比赛链接:https://vjudge.net/contest/750011

A - 烦人的数学作业

是计算数位上数字和类型的数位 DP。

定义 \(dp_{x,y,k}\) 表示当前的上界标记是 \(x\),前导零标记是 \(y\),并且当前枚举到第 \(k\) 位。

为了让后续转移更加便捷,需要最开始预处理一下上界拆位后的情况(考虑使用 \(a\) 数组进行记录),以及需要计算后缀值(定义为 \(f_i\))以处理紧贴上界的情况。

转移的时候根据上界标记枚举当前这一位选择填的数字 \(i\)。对应可以算出 \(x_{new}\)\(y_{new}\),然后调用递归函数直接转移。

但是还需要统计这一轮新加上的答案,并且显然只有当 \(i\) 等于当前需要统计的这种数字 \(num\) 一样的情况下才需要记录。

不过由于有的时候是紧挨上界的,不能直接调用 \(10^{c-1}\) 进行计算(因为有上界限制,没办法容纳下这么大的答案),因此就调用之前预处理好的 \(f\) 后缀数组就可以了。

代码不难写。

B - 数页码

与上题是双倍经验。

少去了多测和 \(L\) 的限制,然后删去取模即可。

C - 数字计数

与上两题是三倍经验。

注意这题和上两题的不同点在于,这题算的是每个数位出现的次数,不需要乘上具体的数值然后加起来算数位上的数字的和。

D - windy 数(加强版)

是加限制的计算方案数类型的数位 DP。

定义状态 \(dp_{i,x,y,k}\) 表示当前考虑了前 \(i\) 位,上下界标记为 \(x\)\(y\),并且当前第 \(i\) 位的值为 \(k\) 的情况下 windy 数的个数。

转移的时候先算出 \(x_{new}\)\(y_{new}\),并且有当前数字 \(k\) 和上一位数字 \(j\),判断一下差是否大于等于 \(2\)。满足条件的话转移就成,\(dp_{i,x_{new},y_{new},k} = dp_{i,x_{new},y_{new},k} + dp_{i-1,x,y,j}\) 即可。

最后的答案就是 \(\sum_{x=0}^{1} \sum_{y=0}^{1} \sum_{k=0}^{9} dp_{n,x,y,k}\)。因为只需要保证走到了最后一步就行,具体情况怒关心。

E - Find Maximum

难点在于转化题意而不在设计算法。

画图和推导,充分理解函数本质——这 \(f(x)\) 说那么复杂,实质上就是算 \(x\) 在三进制下的位数,加上三进制下的各位数字之和。

然后,就可以开始设计算法了。这边采取贪心因为它比较易懂。

就是说,对于每一个数位,考虑两种填法:一种是当前这位少填 \(1\),后面就可以一直填最大的数 \(2\) 了,这样肯定是不错的方案;还有一种的话就是当前这位顶格填,后面就继续维护,视情况选择更优的方案。

可以证明没有方案能够比上面两种情况交替怎么怎么构造出来的答案更优,所以动态维护一下就行了。

应该也可以用数位 DP 解吧。

F - 有趣数字 (Day 2)

是加限制的计算方案数类型的数位 DP。

定义 \(dp_{i,x,y,k}\) 表示当前考虑了前 \(i\) 位,上下界标记 \(x\)\(y\),并且当前这一位上填的数值是 \(k\) 的情况下,满足条件的数字有多少个。

转移的时候和板子很像,只需要判断一下当前位 \(k\) 是否大于等于上一位 \(j\) 就行。满足条件再转移嘛,要不然题目就失去意义了。

答案显然就是 \(\sum_{x=0}^{1} \sum_{y=0}^{1} \sum_{k=0}^{9} dp_{n,x,y,k}\)

G - Round Numbers S

也是加限制的计算方案数类型的数位 DP。

由于处理的是二进制数,一开始得转化一下。

定义 \(dp_{i,x,y,p,q}\) 表示当前考虑到前 \(i\) 位,上下界标记 \(x\)\(y\),并且当前出现了 \(p\)\(0\)\(q\)\(1\)

转移的时候,算出 \(x_{new}\)\(y_{new}\),还得算出 \(p_{new}\)\(q_{new}\)——根据当前位填的这个数值 \(k\) 来判断就行了。不过显然 \(p\) 中不能算进前导零,要不然所有数只要加上足够多的前导零就都能满足条件了,所以得特判一下。

转移式 \(dp_{i,x_{new},y_{new},p_{new},q_{new}} = dp_{i,x_{new},y_{new},p_{new},q_{new}} + dp_{i-1,x,y,p,q}\),是很平常的。

答案 \(\sum_{x=0}^{1} \sum_{y=0}^{1} \sum_{p=0}^{n} \sum_{q=0}^{p} dp_{n,x,y,p,q}\)。至于为什么 \(q\) 的范围必须 \(\le p\),是因为这是题目的限制条件呀。

H - Sam 数

观察题干发现与 D 极其类似,只是把条件颠倒了一下而已。然后看一下数据范围,发现其并没有想象中那么简单,因为位数有 \(10^{18}\)。但是不用担心,只需要后面优化一下就行了。

首先考虑正常的数位 DP 做法。由于没有 \(L\)\(R\) 的限制,没有上下界要处理,甚至没有前导零,因此只需要保证位数是 \(n\) 就可以了。

那 DP 式就可以简洁不少了。\(dp_{i,k}\) 表示当前考虑前 \(i\) 位且第 \(i\) 位值为 \(k\)。转移的时候,枚举 \(i\)\(j\)\(j\) 指上一位的数字。然后枚举这一位的数字 \(k\) 进行转移。由于相邻两个数位的值的差不能超过 \(2\)\(k\) 的取值范围就只能在 \(j-2 \sim j+2\) 里面了。注意不要越界哦。

但是不行,\(n\) 这么大,直接上肯定是过不了的。但是想想优化 DP 的算法,显而易见的,可以想到矩阵快速幂。

转移矩阵比较显然,直接枚举 \(j\)\(k\) 然后按照上面转移的时候所说的方法去机器打表一下就 OK 了。

这个转移矩阵差不多就是长下面这个样子:

\[\begin{bmatrix} 1 & 1 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\ 1 & 1 & 1 & 1 & 0 & 0 & 0 & 0 & 0 & 0\\ 1 & 1 & 1 & 1 & 1 & 0 & 0 & 0 & 0 & 0\\ 0 & 1 & 1 & 1 & 1 & 1 & 0 & 0 & 0 & 0\\ 0 & 0 & 1 & 1 & 1 & 1 & 1 & 0 & 0 & 0\\ 0 & 0 & 0 & 1 & 1 & 1 & 1 & 1 & 0 & 0\\ 0 & 0 & 0 & 0 & 1 & 1 & 1 & 1 & 1 & 0\\ 0 & 0 & 0 & 0 & 0 & 1 & 1 & 1 & 1 & 1\\ 0 & 0 & 0 & 0 & 0 & 0 & 1 & 1 & 1 & 1\\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 1 & 1\\ \end{bmatrix} \]

下标是从 \(0\) 开始的,从 \(0\)\(9\)。是一个二维的。

然后直接做最简单的矩阵快速幂就可以了。

Ex - Daniel and Spring Cleaning

题目就是说,给定一个取值范围 \([l,r]\),问从中选择两个数 \(a\)\(b\),有多少种情况下会满足 \(a \oplus b = a + b\)。考虑 \(a\)\(b\) 的顺序,即 \((1,4)\)\((4,1)\) 算作两种情况。

由于是位运算,首先考虑将其分解成二进制的情况。

同时处理两个数字的上下界显然是特别麻烦的,因此我们可以限制 \(a \le b\),最后将 \(ans \times 2\) 即可。

\(x = y\) 的时候是没有贡献的,因此不用担心,不过 \(a=b=0\) 是有贡献的所以当 \(l = 0\) 的时候得特判一下。

定义 \(dp_{i,x,y,p}\) 表示当前枚举到前 \(i\) 位,上下界标记分别为 \(x\)\(y\),并且两个数是否相等(用 \(p\) 来表示,用处为确定 \(a\) 的具体取值范围)的情况下,存在多少种 \(a \oplus b = a + b\) 的情况。

转移是很简单的,根据 \(x,y,p\) 枚举出 \(a\) 这一位的值 \(v\) 以及 \(b\) 这一位的值 \(u\),然后对应可以算出 \(x_{new}\)\(y_{new}\) 以及 \(p_{new}\),转移 \(dp_{i+1,x_{new},y_{new},p_{new}} = dp_{i+1,x_{new},y_{new},p_{new}} + dp_{i,x,y,p}\)

算答案的时候枚举所有的 \(x,y,p\),然后累加 \(dp_{n,x,y,p}\) 即可。通俗的说,是 \(\sum_{x=0}^{1} \sum_{y=0}^{1} \sum_{p=0}^{1} dp_{n,x,y,p}\)。但是再说一遍,一定要注意特判 \(l=0\) 的情况哦,因为在这种情况下,将 \(ans \times 2\) 之后会把 \(a = b = 0\) 的情况多算一遍,一定要减去多余的那一遍哦!

Ex a lot

再附加几个题,随便提一嘴思路。

https://www.luogu.com.cn/problem/CF1036C

这是一个比较板子的题目,只需要在状态定义里加上“当前出现过多少个非 \(0\) 数字”的这个维度就可以解决了。

https://www.luogu.com.cn/problem/P4124

这个要维护的东西就多一些,首先要考虑维护当前有没有出现过连续的 \(3\) 个相同数(如果还没达到的话,显然是得先维护当前到这个位置时的连续个数的),然后还得用两维分别维护是否出现过 \(8\) 以及是否出现过 \(4\)。最后求解答案的时候也一定要判断一下,首先必须出现过连续 \(3\) 个相同数,而且 \(8\)\(4\) 不能同时出现。细节有点多。

https://www.luogu.com.cn/problem/P3413

简单转化一下题目思路,让判断长度 \(\ge 2\) 的回文改为判断是否存在长度 \(=2\) 或者 \(=3\) 的回文就行。然后就是一定要注意不能把前导零拿来判断回文。为了判断回文,还得记录前两位的值,所以这题的细节也是不少的。

https://www.luogu.com.cn/problem/AT_abc194_f

这个也很简单,它只是打破了常规的十进制和二进制,使用了十六进制来考验。不过为了方便计算当前出现过多少种不同的数位,需要用上状态压缩来判断。

The End

数位 DP 基本上都都一个板子。

比如说状态定义,一般都有当前位数和上下界标记,然后会根据题目的要求加上一些其他辅助转移和求解的维度,通常可能会有当前这一位的具体数值;再打比方说转移,如果是求方案数那么直接算出所有维度新一轮的数值然后累加就行,如果是求数位和可以选择算出每种数字分别出现的次数,然后乘上具体值去累加,如果是求具体数字的和那就只能在转移的时候算下出现次数,乘上 \(10^i\) 最后全部加起来就可以了。

在最后这里放个我之前写过的数位 DP 递推型总结

Thanks reading.

posted @ 2025-09-22 21:31  嘎嘎喵  阅读(63)  评论(0)    收藏  举报