数位dp部分题解
2024-01-26 upd:这篇可以作废了,请移步至数位 dp 学习笔记(灵神模板),该博客介绍了递归的写法,会更容易理解与编写代码。
前言
最近学了一种新的数位dp的状态表示,打算应用到以前做过的数位dp的题目。如果我们对数进行数位dp,以前的状态定义表示所有数位大小为且最高位是数字的数的个数,如果还有其他约束条件那么再补充相应的状态即可。而新的状态定义则是和,其中表示所有数位大小为且值不超过的数的数量,相应的表示所有数位大小为且值超过的数的数量。其中表示在十进制下的最低有效位数字构成的值,例如的最低有效位数字构成的值就是。
下面通过几道题目来运用这种新的做法。
数字游戏
科协里最近很流行数字游戏。
某人命名了一种不降数,这种数字必须满足从左到右各位数字呈非下降关系,如 ,。
现在大家决定玩一个游戏,指定一个整数闭区间 ,问这个区间内有多少个不降数。
输入格式
输入包含多组测试数据。
每组数据占一行,包含两个整数 和 。
输出格式
每行给出一组测试数据的答案,即 之间有多少不降数。
数据范围
输入样例:
1 9 1 19
输出样例:
9 18
解题思路
定义状态满足以下条件的整数的数量:
- 的数位大小为,。
- 若,则;否则。
- 的最高位是数字。
- 从左到右各位数字呈非递降。
对于确定的状态,我们可以枚举数位是的数的最高位是哪个数字(记作)来转移到数位大小是的状态,状态转移方程就是
其中表示的是在十进制下从低位(第位)开始的第位上的数字。然后再考虑一下是否需要处理前导的情况,可以发现包含前导并不会影响答案,因为包含前导的数仍然满足从左到右各位数字呈非下降关系。因此最终答案就是,其中是在十进制下的数位个数。
AC代码如下,时间复杂度为:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 12; 5 6 int f[N][2][10]; 7 int p[N], sz; 8 9 int get(int x) { 10 if (!x) return 1; 11 sz = 0; 12 while (x) { 13 p[sz++] = x % 10; 14 x /= 10; 15 } 16 memset(f, 0, sizeof(f)); 17 for (int i = 0; i <= 9; i++) { 18 f[1][i <= p[0]][i]++; 19 } 20 for (int i = 1; i < sz; i++) { 21 for (int j = 0; j <= 1; j++) { 22 for (int k = 0; k <= 9; k++) { 23 for (int u = 0; u <= k; u++) { 24 f[i + 1][u < p[i] || u == p[i] && j][u] += f[i][j][k]; 25 } 26 } 27 } 28 } 29 int ret = 0; 30 for (int i = 0; i <= 9; i++) { 31 ret += f[sz][1][i]; 32 } 33 return ret; 34 } 35 36 int main() { 37 int a, b; 38 while (~scanf("%d %d", &a, &b)) { 39 printf("%d\n", get(b) - get(a - 1)); 40 } 41 42 return 0; 43 }
Windy数
Windy 定义了一种 Windy 数:不含前导零且相邻两个数字之差至少为 的正整数被称为 Windy 数。
Windy 想知道,在 和 之间,包括 和 ,总共有多少个 Windy 数?
输入格式
共一行,包含两个整数 和 。
输出格式
输出一个整数,表示答案。
数据范围
输入样例1:
1 10
输出样例1:
9
输入样例2:
25 50
输出样例2:
20
解题思路
定义状态满足以下条件的整数的数量:
- 的数位大小为,。
- 若,则;否则。
- 的最高位是数字。
- 的所有相邻数位上的数相差至少为。
状态转移方程为
这里就需要处理前导的情况,这是因为前导可能会使得数不满足相邻两个数字之差至少为这个条件,比如是满足条件的,而就不满足条件了。为了避免前导的情况,这时我们只需分别考虑每一个数位大小,统计最高位的数字不是的情况即可,有。除此之外,当数位大小不足,即时,我们默认往高位补(并非添加前导),这时不管最低位怎么填数字,整个数都不会超过,因此还需要加上。因此最终答案就是
AC代码如下,时间复杂度为:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 15; 5 6 int f[N][2][10]; 7 int p[N], sz; 8 9 int get(int x) { 10 if (!x) return 0; 11 sz = 0; 12 while (x) { 13 p[sz++] = x % 10; 14 x /= 10; 15 } 16 memset(f, 0, sizeof(f)); 17 for (int i = 0; i <= 9; i++) { 18 f[1][i <= p[0]][i]++; 19 } 20 for (int i = 1; i < sz; i++) { 21 for (int j = 0; j <= 1; j++) { 22 for (int k = 0; k <= 9; k++) { 23 for (int u = 0; u <= 9; u++) { 24 if (abs(k - u) >= 2) f[i + 1][u < p[i] || u == p[i] && j][u] += f[i][j][k]; 25 } 26 } 27 } 28 } 29 int ret = 0; 30 for (int i = 1; i <= sz; i++) { 31 for (int j = 1; j <= 9; j++) { 32 ret += f[i][1][j]; 33 if (i < sz) ret += f[i][0][j]; 34 } 35 } 36 return ret; 37 } 38 39 int main() { 40 int a, b; 41 scanf("%d %d", &a, &b); 42 printf("%d", get(b) - get(a - 1)); 43 44 return 0; 45 }
数字游戏 II
由于科协里最近真的很流行数字游戏。
某人又命名了一种取模数,这种数字必须满足各位数字之和 为 。
现在大家又要玩游戏了,指定一个整数闭区间 ,问这个区间内有多少个取模数。
输入格式
输入包含多组测试数据,每组数据占一行。
每组数据包含三个整数 。
输出格式
对于每个测试数据输出一行结果,表示区间内各位数字和 为 的数的个数。
数据范围
,
输入样例:
1 19 9
输出样例:
2
解题思路
定义状态满足以下条件的整数的数量:
- 的数位大小为,。
- 若,则;否则。
- 的各位数字之和模为。
状态转移方程为
可以发现不需要处理前导的情况,因为对求和后并不会影响模的结果。因此最终答案就是。
AC代码如下,时间复杂度为:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 15, M = 110; 5 6 int a, b, m; 7 int f[N][2][M]; 8 int p[N], sz; 9 10 int get(int x) { 11 if (!x) return 1; 12 sz = 0; 13 while (x) { 14 p[sz++] = x % 10; 15 x /= 10; 16 } 17 memset(f, 0, sizeof(f)); 18 for (int i = 0; i <= 9; i++) { 19 f[1][i <= p[0]][i % m]++; 20 } 21 for (int i = 1; i < sz; i++) { 22 for (int j = 0; j <= 1; j++) { 23 for (int k = 0; k < m; k++) { 24 for (int u = 0; u <= 9; u++) { 25 f[i + 1][u < p[i] || u == p[i] && j][(u + k) % m] += f[i][j][k]; 26 } 27 } 28 } 29 } 30 return f[sz][1][0]; 31 } 32 33 int main() { 34 while (~scanf("%d %d %d", &a, &b, &m)) { 35 printf("%d\n", get(b) - get(a - 1)); 36 } 37 38 return 0; 39 }
不要62
杭州人称那些傻乎乎粘嗒嗒的人为 (音:laoer)。
杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除个别的士司机和乘客的心理障碍,更安全地服务大众。
不吉利的数字为所有含有 或 的号码。例如: 都属于不吉利号码。但是, 虽然含有 和 ,但不是 连号,所以不属于不吉利数字之列。
你的任务是,对于每次给出的一个牌照号区间 ,推断出交管局今后又要实际上给多少辆新的士车上牌照了。
输入格式
输入包含多组测试数据,每组数据占一行。
每组数据包含一个整数对 和 。
当输入一行为“0 0”时,表示输入结束。
输出格式
对于每个整数对,输出一个不含有不吉利数字的统计个数,该数值占一行位置。
数据范围
输入样例:
1 100 0 0
输出样例:
80
解题思路
定义状态满足以下条件的整数的数量:
- 的数位大小为,。
- 若,则;否则。
- 的最高位是数字。
- 中不含有数字以及相邻的。
状态转移方程为
可以发现不需要处理前导的情况,因此最终答案就是。
AC代码如下,时间复杂度为:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 15; 5 6 int f[N][2][N]; 7 int p[N], sz; 8 9 int get(int x) { 10 if (!x) return 1; 11 sz = 0; 12 while (x) { 13 p[sz++] = x % 10; 14 x /= 10; 15 } 16 memset(f, 0, sizeof(f)); 17 for (int i = 0; i <= 9; i++) { 18 if (i != 4) f[1][i <= p[0]][i]++; 19 } 20 for (int i = 1; i < sz; i++) { 21 for (int j = 0; j <= 1; j++) { 22 for (int k = 0; k <= 9; k++) { 23 for (int u = 0; u <= 9; u++) { 24 if (u == 6 && k == 2 || u == 4) continue; 25 f[i + 1][u < p[i] || u == p[i] && j][u] += f[i][j][k]; 26 } 27 } 28 } 29 } 30 int ret = 0; 31 for (int i = 0; i <= 9; i++) { 32 ret += f[sz][1][i]; 33 } 34 return ret; 35 } 36 37 int main() { 38 int a, b; 39 while (scanf("%d %d", &a, &b), a) { 40 printf("%d\n", get(b) - get(a - 1)); 41 } 42 43 return 0; 44 }
参考资料
F - Nim Editorial:https://atcoder.jp/contests/abc317/editorial/7047
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/17661450.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
2022-08-28 Build a Matrix With Conditions
2022-08-28 整数分组