当前位置: 首页 > news >正文

【1 月小记】Part 4: 数位 DP - L

数位 DP

持续更新中……

一、导言

数位 DP 是一种解决“统计合法数字的个数”一类问题的动态规划方法。

这种数字可以是任意进制的。

这种问题一般具有以下特征:

  1. 最终目的为计数;
  2. 可以用拆位的思想解决;
  3. 统计限制为给定区间;
  4. 上界很大。

基本原理(引用自 OI-Wiki):考虑人类计数的方式,最朴素的计数就是从小到大开始依次加一。但我们发现对于位数比较多的数,这样的过程中有许多重复的部分,例如,从 7000 数到 7999、从 8000 数到 8999、和从 9000 数到 9999 的过程非常相似,它们都是后三位从 000 变到 999,不一样的地方只有千位这一位,所以我们可以把这些过程归并起来,将这些过程中产生的计数答案也都存在一个通用的数组里。此数组根据题目具体要求设置状态,用递推或 DP 的方式进行状态转移。

数位 DP 中通常会利用常规计数问题技巧,比如把一个区间内的答案拆成两部分相减。

二、例题

P2602 [ZJOI2010] 数字计数

这是一道数位 DP 的模板题。

我们充分发扬人类智慧,使用差分思想,发现题意需要转化一下。让求 \([a, b]\) 的数字中,出现了几次某个数字,实际就是求 \([0, b]\) 中出现了几次某个数字,减去 \([0, a - 1]\) 中出现了几次某个数字。

不妨使用记忆化搜索解决此题。搜索函数 DFS 中传入以下参量:

  1. \(pos\),表示当前搜索的数字位数

  2. \(lmt\),表示当前数字是否还在限制范围内。意义:如果 \(lmt\) 为 true,表示当前数字还在限制范围内,接下来可以选择的数字范围是 \([0, num[pos]]\);如果 \(lmt\) 为 false,表示当前数字已经超出了限制范围,接下来可以选择的数字范围是 \([0, 9]\)

  3. \(sum\),表示当前数字中出现了多少个目标数字,相当于对 \([0, num]\) 中的每一位进行统计。

  4. \(zero\),表示当前数字前面是否有零。意义:免去前导零带来的麻烦,即如果 \(zero\) 为 true,表示当前数字前面有零,接下来可以选择的数字范围是 \([1, 9]\);如果 \(zero\) 为 false,表示当前数字前面没有零,接下来可以选择的数字范围是 \([0, 9]\)

  5. \(digit\),表示当前要统计的目标数字。

我们使用 \(f\) 数组记忆化存储状态信息,初始值设为 \(-1\)

搜索边界为 \(pos = 0\),此时返回 \(sum\)

在搜索过程中定义一个结果变量 \(res\)。依次枚举 \([0, 9]\) 中的每一个数字 \(i\),判断当前数字是否在限制范围内,并进行递归搜索。以下是搜索细节:

  1. 如果 \(lmt\) 为 false,且 \(i > num[pos]\),则 break,因为当前数字已经超出了限制范围。

  2. 递归调用 DFS 函数,传入以下参数:

    a. \(pos - 1\),表示搜索下一位数字。

    b. \(lmt \lor (i < num[pos])\),表示当前数字是否还在限制范围内。如果 \(lmt\) 为 true,且 \(i < num[pos]\),则表示当前数字已经超出了限制范围,传入 false;否则传入 true。

    c. \(sum + ((\lnot zero \lor i) \land (i = digit))\),表示当前数字中出现了多少个目标数字。如果 \(zero\) 为 false 或 \(i\) 不为 0,且 \(i\) 等于 \(digit\),则表示当前数字中出现了一个目标数字,\(sum\) 加 1;否则 \(sum\) 不变。

    d. \(zero \land (i == 0)\),表示当前数字前面是否有零。如果 \(zero\) 为 true 且 \(i\) 为 0,则表示当前数字前面有零,传入 true;否则传入 false。

    e. \(digit\),不变。

最后,返回结果 \(res\)

对于输入的两个整数 \(a\)\(b\),写一个函数 work 分解它们的十进制位,并将这些数位存到全局数组 \(num\) 中,随后调用 DFS 函数进行搜索。

代码如下:

#include <bits/stdc++.h>
#define int long long
#define inf LONG_LONG_MAX
#define debug cout << '!';
using namespace std;
int a, b;
int num[20];
int f[20][2][200][2][10];
int DFS(int pos, bool lmt, int sum, bool zero, int digit) {if (pos == 0) return sum;if (f[pos][lmt][sum][zero][digit] != -1) return f[pos][lmt][sum][zero][digit];int res = 0;for (int i = 0; i <= 9; i++) {if (!lmt && i > num[pos]) break;res += DFS(pos - 1, lmt || (i < num[pos]), sum + ((!zero || i) && (i == digit)), zero && (i == 0), digit);}f[pos][lmt][sum][zero][digit] = res;return res;
}
int work(int x, int digit) {int len = 0;while (x) {num[++len] = x % 10;x /= 10;}memset(f, -1, sizeof(f));return DFS(len, 0, 0, 1, digit);
}
signed main() {cin.tie(0) -> sync_with_stdio(0);cin >> a >> b;for (int i = 0; i <= 9; i++) {cout << work(b, i) - work(a - 1, i) << ' ';}return 0;
}

P3413 SAC#1 - 萌数

我们要找出所有萌的数。题意告诉我们,一个数字是萌的,当且仅当它的十进制数码中,存在长度至少为 \(2\) 的回文子串。

但是,我们注意到,一个长度大于 \(2\) 的回文子串内:

  1. 如果这个这个回文子串的长度为偶数,那么它必定包含一个长度严格为 \(2\) 的回文子串;

  2. 如果这个回文子串的长度为奇数,那么它必定包含一个长度严格为 \(3\) 的回文子串。

因此,我们只需要找到所有包含长度为 \(2\)\(3\) 的回文子串的数字即可。

这显然是一道数位 DP,所以我们考虑使用记忆化搜索求解。

DFS 内需要传入的参量有:

  1. \(pos\),表示当前搜到数字的位数;

  2. \(lmt\),表示当前数字的上限;

  3. \(pre1\),表示前一位的数字;

  4. \(pre2\),表示前两位的数字;

  5. \(zero\),表示当前数字是否包含前导零;

  6. \(moe\),表示当前数字是否包含萌的子串。

在 DFS 的过程中,如果一位数码和前一位的数字相同,那么就找到了一个长度为 \(2\) 的回文子串;如果和前两位的数字相同,那么就找到了一个长度为 \(3\) 的回文子串。在找到这样的子串后,我们可以直接返回 true,并将 \(moe\) 设为 true,表示当前数字是萌的。

记得取模。

P13085 [SCOI2009] windy 数(加强版)

比较板子的一题。

与上一题基本一致,但搜索时不用传两个 \(pre\) 变量,而只需判断当前数位与上一位的差与 \(2\) 的关系即可。

http://www.jsqmd.com/news/254786/

相关文章:

  • 2025年湖南大学计算机考研复试机试真题(解题思路 + AC 代码)
  • 2026最新31888标准面料推荐!国内优质面料品牌权威榜单发布,资质与品质双优助力纺织行业高质量发展 - 品牌推荐2026
  • 2026年AI智能软硬件开发十大排名权威发布
  • 2025年华东师范大学计算机考研复试机试真题(解题思路 + AC 代码)
  • 吴恩达深度学习课程五:自然语言处理 第二周:词嵌入(一)词汇表征和类比推理
  • 实用指南:glTF PBR材质 / 3ds Max设置导入导出glb/gltf
  • 一款专为 WinUI XAML 设计的快速原型设计工具,生成的代码可轻松复制到Visual Studio中!
  • nodejs基于JavaScript的礼物赠送系统_0v80400r
  • 10 个常用在线简历制作网站体验对比,新手也能快速上手
  • Springboot《非遗之美》非物质文化遗产系统 Web项目开发可视化大屏_459w5ar6
  • 函数指针数组
  • 基于改进遗传算法的配电网故障定位Matlab代码
  • 2026国内最新纯棉绣花面料品牌top10推荐!广东广州等地优质纯棉绣花面料企业权威榜单发布,品质工艺双优助力服饰升级国内 - 品牌推荐2026
  • springboot大学生课程提醒系统_1fj8z5gv
  • 瑞芯微(EASY EAI)RV1126B 车辆检测
  • 2026最新冲锋衣面料推荐!国内优质冲锋衣面料权威榜单发布,品质功能双优助力户外服饰升级冲锋衣面料推荐 - 品牌推荐2026
  • 别一上来就 DFS:聊聊「以图判树」背后的算法直觉(Graph Valid Tree)
  • 亲测好用10个AI论文网站,专科生搞定毕业论文必备!
  • 别一听区块链就上来挖矿:聊聊它在智能运维里的“正经用法”
  • 2026最新涤盖棉面料推荐!国内优质涤盖棉权威榜单发布,品质与功能双优助力服饰升级涤盖棉面料公司推荐 - 品牌推荐2026
  • Kafka 消息不丢、不乱、不崩的秘密——聊聊我是怎么把 Kafka 的“稳定性”一点点熬出来的
  • Python 与 AI 药物开发:从试验室到代码实践的深度探索
  • 2026最新产业高质量发展服务推荐!国内农业特色产业/区域特色农业/农产品品牌建设权威指南发布,专业赋能助力乡村振兴 - 品牌推荐2026
  • 2026最新空气层面料推荐!国内优质空气层面料权威榜单发布,品质与功能兼具助力纺织行业升级 空气层面料推荐 - 品牌推荐2026
  • 生成式AI在教育资源生成中的应用探索
  • java+vue基于Spring Boot的工程流程控制系统_x147jv9t
  • python基于django的智慧党建平台设计与实现
  • Python 在 AI 芯片管理中的实战指南——从监控调度到智能优化,让异构算力不再“黑盒”
  • 2026最新校服面料推荐!行业权威榜单发布,安全舒适功能性面料品牌推荐 - 品牌推荐2026
  • springboot-vue大学生社团管理系统_254x2yk1