数位dp部分题解

  2024-01-26 upd:这篇可以作废了,请移步至数位 dp 学习笔记(灵神模板),该博客介绍了递归的写法,会更容易理解与编写代码。

前言

  最近学了一种新的数位dp的状态表示,打算应用到以前做过的数位dp的题目。如果我们对数N进行数位dp,以前的状态定义f(i,j)表示所有数位大小为i且最高位是数字j的数的个数,如果还有其他约束条件那么再补充相应的状态即可。而新的状态定义则是f(i,1)f(i,0),其中f(i,1)表示所有数位大小为i且值不超过Nmod10i的数的数量,相应的f(i,0)表示所有数位大小为i且值超过Nmod10i的数的数量。其中Nmod10i表示N在十进制下的最低有效i位数字构成的值,例如123456789的最低有效6位数字构成的值就是456789

  下面通过几道题目来运用这种新的做法。

 

数字游戏

科协里最近很流行数字游戏。

某人命名了一种不降数,这种数字必须满足从左到右各位数字呈非下降关系,如 123446

现在大家决定玩一个游戏,指定一个整数闭区间 [a,b],问这个区间内有多少个不降数。

输入格式

输入包含多组测试数据。

每组数据占一行,包含两个整数 ab

输出格式

每行给出一组测试数据的答案,即 [a,b] 之间有多少不降数。

数据范围

1ab2311

输入样例:

1 9
1 19

输出样例:

9
18

 

解题思路

  定义状态f(i,j,k)满足以下条件的整数n的数量:

  • n的数位大小为i0n<10i
  • nNmod10i,则j=1;否则j=0
  • n的最高位是数字k
  • n从左到右各位数字呈非递降。

  对于确定的状态f(i,j,k),我们可以枚举数位是i+1的数的最高位是哪个数字(记作u)来转移到数位大小是i+1的状态,状态转移方程就是f(i,j,k)f(i+1, u<pi or u=pi and j, u)(uk)

  其中pi表示的是N在十进制下从低位(第0位)开始的第i位上的数字。然后再考虑一下是否需要处理前导0的情况,可以发现包含前导0并不会影响答案,因为包含前导0的数仍然满足从左到右各位数字呈非下降关系。因此最终答案就是i=09f(sz,1,i),其中szN在十进制下的数位个数。

  AC代码如下,时间复杂度为O(102logN)

复制代码
 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 数:不含前导零且相邻两个数字之差至少为 2 的正整数被称为 Windy 数。

Windy 想知道,在 AB 之间,包括 AB,总共有多少个 Windy 数?

输入格式

共一行,包含两个整数 AB

输出格式

输出一个整数,表示答案。

数据范围

1AB2×109

输入样例1:

1 10

输出样例1:

9

输入样例2:

25 50

输出样例2:

20

 

解题思路

  定义状态f(i,j,k)满足以下条件的整数n的数量:

  • n的数位大小为i0n<10i
  • nNmod10i,则j=1;否则j=0
  • n的最高位是数字k
  • n的所有相邻数位上的数相差至少为2

  状态转移方程为f(i,j,k)f(i+1, u<pi or u=pi and j, u)(|uk|2)

  这里就需要处理前导0的情况,这是因为前导0可能会使得数不满足相邻两个数字之差至少为2这个条件,比如15是满足条件的,而015就不满足条件了。为了避免前导0的情况,这时我们只需分别考虑1sz每一个数位大小,统计最高位的数字不是0的情况即可,有i=1szj=19f(i,1,j)。除此之外,当数位大小不足sz,即i<sz时,我们默认往高位补0(并非添加前导0),这时不管最低i位怎么填数字,整个数都不会超过N,因此还需要加上i=1sz1j=19f(i,0,j)。因此最终答案就是i=1szj=19f(i,1,j)+i=1sz1j=19f(i,0,j)

  AC代码如下,时间复杂度为O(102logN)

复制代码
 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

由于科协里最近真的很流行数字游戏。

某人又命名了一种取模数,这种数字必须满足各位数字之和 mod N0

现在大家又要玩游戏了,指定一个整数闭区间 [a.b],问这个区间内有多少个取模数。

输入格式

输入包含多组测试数据,每组数据占一行。

每组数据包含三个整数 a,b,N

输出格式

对于每个测试数据输出一行结果,表示区间内各位数字和 mod N0 的数的个数。

数据范围

1a,b2311,
1N<100

输入样例:

1 19 9

输出样例:

2

 

解题思路

  定义状态f(i,j,k)满足以下条件的整数n的数量:

  • n的数位大小为i0n<10i
  • nNmod10i,则j=1;否则j=0
  • n的各位数字之和模mk

  状态转移方程为f(i,j,k)f(i+1, u<pi or u=pi and j, (u+k)modm)

  可以发现不需要处理前导0的情况,因为对0求和后并不会影响模m的结果。因此最终答案就是f(sz,1,0)

  AC代码如下,时间复杂度为O(10mlogN)

复制代码
 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

杭州人称那些傻乎乎粘嗒嗒的人为 62(音:laoer)。

杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除个别的士司机和乘客的心理障碍,更安全地服务大众。

不吉利的数字为所有含有 462 的号码。例如:62315,73418,88914 都属于不吉利号码。但是,61152 虽然含有 62,但不是 连号,所以不属于不吉利数字之列。

你的任务是,对于每次给出的一个牌照号区间 [n,m],推断出交管局今后又要实际上给多少辆新的士车上牌照了。

输入格式

输入包含多组测试数据,每组数据占一行。

每组数据包含一个整数对 nm

当输入一行为“0 0”时,表示输入结束。

输出格式

对于每个整数对,输出一个不含有不吉利数字的统计个数,该数值占一行位置。

数据范围

1nm109

输入样例:

1 100
0 0

输出样例:

80

 

解题思路

  定义状态f(i,j,k)满足以下条件的整数n的数量:

  • n的数位大小为i0n<10i
  • nNmod10i,则j=1;否则j=0
  • n的最高位是数字k
  • n中不含有数字4以及相邻的62

  状态转移方程为f(i,j,k)f(i+1, u<pi or u=pi and j, u)(u4 and ¬(u=6 and k=2))

  可以发现不需要处理前导0的情况,因此最终答案就是i=09f(sz,1,i)

  AC代码如下,时间复杂度为O(102logN)

复制代码
 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

posted @   onlyblues  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
历史上的今天:
2022-08-28 Build a Matrix With Conditions
2022-08-28 整数分组
Web Analytics
点击右上角即可分享
微信分享提示