Number of Beautiful Integers in the Range

Number of Beautiful Integers in the Range

You are given positive integers lowhigh, and k.

A number is beautiful if it meets both of the following conditions:

  • The count of even digits in the number is equal to the count of odd digits.
  • The number is divisible by k.

Return the number of beautiful integers in the range [low, high].

Example 1:

复制代码
Input: low = 10, high = 20, k = 3
Output: 2
Explanation: There are 2 beautiful integers in the given range: [12,18]. 
- 12 is beautiful because it contains 1 odd digit and 1 even digit, and is divisible by k = 3.
- 18 is beautiful because it contains 1 odd digit and 1 even digit, and is divisible by k = 3.
Additionally we can see that:
- 16 is not beautiful because it is not divisible by k = 3.
- 15 is not beautiful because it does not contain equal counts even and odd digits.
It can be shown that there are only 2 beautiful integers in the given range.
复制代码

Example 2:

Input: low = 1, high = 10, k = 1
Output: 1
Explanation: There is 1 beautiful integer in the given range: [10].
- 10 is beautiful because it contains 1 odd digit and 1 even digit, and is divisible by k = 1.
It can be shown that there is only 1 beautiful integer in the given range.

Example 3:

Input: low = 5, high = 5, k = 2
Output: 0
Explanation: There are 0 beautiful integers in the given range.
- 5 is not beautiful because it is not divisible by k = 2 and it does not contain equal even and odd digits.

Constraints:

  • 0 < low <= high <= 109
  • 0 < k <= 20

 

解题思路

  一碰到数位dp就谔谔,昨晚硬是磨了一个多小时都没做出来,然后今天调了一早上才过了,被傻逼lc的评测机制气乐了。

  数位dp我一直都用递推去做的,记忆化搜索一直没学会。当时想状态定义的时候就想了很久,现在的总结就是其实只用关注从最高位往低位递推时会用到哪些特定的数,然后把需要的状态定义出来就好了,比如说最基本的数位的个数,最高位是哪一个数,然后是其他特性比如这题中奇偶数位分别有多少,整个数模k是多少。

  定义状态f(i,j,u,v,w)表示所有数位大小为i,最高位是j,奇数数位有u个,偶数数位有v个,整个数模kw的数的个数。很明显数位大小为i的数可以转移到数位大小为i+1的数,因此当前状态f(i,j,u,v,w)可以转移到的状态就有{f(i+1, x, u+1, v, (x10i+w)modk)x{1,3,5,7,9}f(i+1, x, u, v+1, (x10i+w)modk)x{0,2,4,6,8}

  然后递推的时候求的是1x中有多少个数满足条件,先把x中的每一个数分离出来,然后从最高位开始往底位枚举。只有x的数位大小为偶数时才能递推,因为要求奇偶数位相同,并且在递推时要保证没有前导0(前导0会印象奇偶数位的数目),最后再把那些含前导0的数补上即可。递推时需要开变量分别记录前面已确认的数的奇数数位和偶数数位有多少,以及组成的数在十进制中模k是多少。

  最后因为lc的逆天评测机制,如果对于每个样例都跑dp预处理那么肯定会TLE,因此需要把预处理的结果记录到全局变量中,这样只用跑一次即可,不过需要把1k20所有情况都跑一遍,预处理的计算量大概是20×10×10×6×6×20×10107

  AC代码如下:

复制代码
 1 vector<vector<vector<vector<vector<vector<int>>>>>> f;
 2 
 3 class Solution {
 4 public:
 5     void init() {
 6         f = vector<vector<vector<vector<vector<vector<int>>>>>>(21, vector<vector<vector<vector<vector<int>>>>>(11, vector<vector<vector<vector<int>>>>(10, vector<vector<vector<int>>>(6, vector<vector<int>>(6, vector<int>(20))))));
 7         for (int k = 1; k <= 20; k++) {
 8             vector<int> p(10);
 9             p[0] = 1 % k;
10             for (int i = 1; i < 10; i++) {
11                 p[i] = p[i - 1] * 10 % k;
12             }
13             for (int i = 0; i <= 9; i++) {
14                 if (i & 1) f[k][1][i][1][0][i % k] = 1;
15                 else f[k][1][i][0][1][i % k] = 1;
16             }
17             for (int i = 1; i < 10; i++) {
18                 for (int j = 0; j <= 9; j++) {
19                     for (int u = 0; u <= 5; u++) {
20                         for (int v = 0; v <= 5; v++) {
21                             for (int w = 0; w < k; w++) {
22                                 for (int x = 0; x <= 9; x++) {
23                                     if (x % 2 && u + 1 <= 5) f[k][i + 1][x][u + 1][v][(x * p[i] + w) % k] += f[k][i][j][u][v][w];
24                                     else if (x % 2 == 0 && v + 1 <= 5) f[k][i + 1][x][u][v + 1][(x * p[i] + w) % k] += f[k][i][j][u][v][w];
25                                 }
26                             }
27                         }
28                     }
29                 }
30             }
31         }
32     }
33     
34     int numberOfBeautifulIntegers(int low, int high, int k) {
35         if (f.empty()) init();
36         vector<int> p(10);
37         p[0] = 1 % k;
38         for (int i = 1; i < 10; i++) {
39             p[i] = p[i - 1] * 10 % k;
40         }
41         function<int(int)> get = [&](int x) {
42             if (!x) return 0;
43             vector<int> q;
44             while (x) {
45                 q.push_back(x % 10);
46                 x /= 10;
47             }
48             int ret = 0, odd = 0, even = 0, s = 0;
49             if (~q.size() & 1) {
50                 int t = q.size() >> 1;
51                 for (int i = q.size() - 1; i >= 0; i--) {
52                     for (int j = i == q.size() - 1; j < q[i]; j++) {
53                         ret += f[k][i + 1][j][t - odd][t - even][(k - s) % k];
54                     }
55                     if (q[i] & 1) odd++;
56                     else even++;
57                     if (odd > t || even > t) break;
58                     s = (s + q[i] * p[i]) % k;
59                     if (!i && odd == even && !s) ret++;
60                 }
61             }
62             for (int i = 1; i < q.size(); i++) {
63                 if (~i & 1) {
64                     for (int j = 1; j <= 9; j++) {
65                         ret += f[k][i][j][i >> 1][i >> 1][0];
66                     }
67                 }
68             }
69             return ret;
70         };
71         return get(high) - get(low - 1);
72     }
73 };
复制代码

  这题还可以用暴搜,可以发现只有数位大小为偶数的数才能满足奇数数位和偶数数位的数目相同,因此可以暴搜出来这些数,实际上大概有2×107个左右。然后在暴搜的时候要保证搜到的数有序,对于lowhigh二分出可选数的区间,然后再逐个枚举是否模k0即可。详细见参考资料,这里就不写了,比赛很少会这样做,一般都直接数位dp。

  2023-08-27更新:

  受F - Nim这题的启发,题解中用到了我没接触过的新的dp状态表示,这里把这种新的状态定义用到这题。之后再把atc这题的题解补上。

  对N进行数位dp,在1N中求出所有满足条件的数的数量。定义状态f(i,j,u,v,w,x)表示满足以下条件的整数n(十进制)的数量:

  • n的数位大小为i,或者说0n<10i
  • nNmod10i,则j=1;否则j=0。其中Nmod10i表示N在十进制下的最低有效i位数字构成的值,例如123456789的最低有效6位数字构成的值就是456789
  • ni个数位中有u个数位是奇数,v个数位是偶数。
  • nmodk=w
  • n的最高数位上的数是0,则x=0;否则x=1

  这里解释一下为什么要加一个表示最高位上的数是否为0的状态。这是因为在统计时数不能有前导0,因此加上这个状态以示区别。  

  对于当前确定的状态f(i,j,u,v,w,x),我们可以转移到数位是i+1的状态,只需枚举数位是i+1的数的最高位是哪个数字即可(记作y),状态转移方程就是{f(i,j,u,v,w,x)f(i+1, y<pi or y=pi and j, u+1, v, (y10i+w)modw, ¬¬y)y{0,2,4,6,8}f(i,j,u,v,w,x)f(i+1, y<pi or y=pi and j, u, v+1, (y10i+w)modw, ¬¬y)y{1,3,5,7,9}

  其中pi表示的是N在十进制下从低位开始的第i位上的数字。¬¬y表示的是,如果y=0,则结果为0;如果y>0,则结果为1

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

复制代码
 1 class Solution {
 2 public:
 3     int numberOfBeautifulIntegers(int low, int high, int k) {
 4         function<int(int)> get = [&](int x) {
 5             if (!x) return 0;
 6             vector<int> p;
 7             while (x) {
 8                 p.push_back(x % 10);
 9                 x /= 10;
10             }
11             int sz = p.size();
12             vector<vector<vector<vector<vector<vector<long long>>>>>> f(sz + 1, vector<vector<vector<vector<vector<long long>>>>>(2, vector<vector<vector<vector<long long>>>>(sz / 2 + 2, vector<vector<vector<long long>>>(sz / 2 + 2, vector<vector<long long>>(k, vector<long long>(2))))));
13             for (int i = 0; i <= 9; i++) {
14                 if (i & 1) f[1][i <= p[0]][1][0][i % k][!!i]++;
15                 else f[1][i <= p[0]][0][1][i % k][!!i]++;
16             }
17             vector<int> p10(sz);
18             p10[0] = 1;
19             for (int i = 1; i < sz; i++) {
20                 p10[i] = (p10[i - 1] * 10) % k;
21             }
22             for (int i = 1; i < sz; i++) {
23                 for (int j = 0; j <= 1; j++) {
24                     for (int u = 0; u <= sz >> 1; u++) {
25                         for (int v = 0; v <= sz >> 1; v++) {
26                             for (int w = 0; w < k; w++) {
27                                 for (int x = 0; x <= 1; x++) {
28                                     for (int y = 0; y <= 9; y++) {
29                                         if (y & 1) f[i + 1][y < p[i] || y == p[i] && j][u + 1][v][(y * p10[i] + w) % k][!!y] += f[i][j][u][v][w][x];
30                                         else f[i + 1][y < p[i] || y == p[i] && j][u][v + 1][(y * p10[i] + w) % k][!!y] += f[i][j][u][v][w][x];
31                                     }
32                                 }
33                             }
34                         }
35                     }
36                 }
37             }
38             int ret = 0;
39             for (int i = 2; i <= sz; i += 2) {  // 只考虑位数个数是偶数的数
40                 ret += f[i][1][i >> 1][i >> 1][0][1];   // 在位数为i的数中,值不超过N的最低i位,且最高位不为0的数的个数
41                 if (i < sz) ret += f[i][0][i >> 1][i >> 1][0][1];   // 由于数位长度不到sz,故高位用0补充,低位的部分可以超过N的最低i位,而总体(高位补0后长度为sz的数)不超过N,故这部分的数需要加上
42             }
43             return ret;
44         };
45         return get(high) - get(low - 1);
46     }
47 };
复制代码

 

参考资料

  久违的力扣周赛讲解来啦~第111场双周赛:https://www.bilibili.com/video/BV1P44y1F7EG/

  F - Nim Editorial:https://atcoder.jp/contests/abc317/editorial/7047

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