什么是计数问题?

303 阅读4分钟

前言:什么是计数问题

计数问题是统计一个数字x,统计他从1到y中在每一位中总共出现过的次数;

例如我统计1在1-11中出现过的次数,答案就是1中1个,10有一个,11有两个,总共1-11中1这个数字总共出现了1+1+2=4次

举例说明

1、题目描述

给定两个整数 a 和 b ,求 a 和 b 之间的所有数字中 0 ~ 9 的出现次数。

例如,a=1024,b=1032,则 a 和 b 之间共有 9 个数如下:

1024 1025 1026 1027 1028 1029 1030 1031 1032

其中 0 出现 10 次,1 出现 10 次,2 出现 7 次,3 出现 3次等等……

2、输入格式

输入包含多组测试数据。

每组测试数据占一行,包含两个整数 a 和 b。

当读入一行为 0 0 时,表示输入终止,且该行不作处理。

3、输出格式

每组数据输出一个结果,每个结果占一行。

每个结果包含十个用空格隔开的数字,第一个数字表示 0 出现的次数,第二个数字表示 1 出现的次数,以此类推。

4、输入样例

1 10
44 497
346 542
1199 1748
1496 1403
1004 503
1714 190
1317 854
1976 494
1001 1960
0 0

5、输出样例

1 2 1 1 1 1 1 1 1 1
85 185 185 185 190 96 96 96 95 93
40 40 40 93 136 82 40 40 40 40
115 666 215 215 214 205 205 154 105 106
16 113 19 20 114 20 20 19 19 16
107 105 100 101 101 197 200 200 200 200
413 1133 503 503 503 502 502 417 402 412
196 512 186 104 87 93 97 97 142 196
398 1375 398 398 405 499 499 495 488 471
294 1256 296 296 296 296 287 286 286 247

6、算法

(数位DP,暴力枚举) O(log10(b)10)O(log10(b)∗10)

这是一道比较典型的数位 DP 的题目,需要分别求出 0~9 在 [1,b] 中的出现次数和在 [1,a-1] 中的出现次数,然后相减即可得到 [a,b] 中的出现次数。具体实现时,可以采用以下方法:

  • 对于一个数字 n,将其分解为若干个数字 d1,d2,d3,...,dk,即 n=d110^(k-1)+d210^(k-2)+...+dk*10^0。
  • 对于每个数字 i,设 cnt(i,n) 表示在 [1,n] 中 i 的出现次数,则 cnt(i,n) 可以通过如下方式
  • 接下来我们来看看代码中的 dgt 函数,这个函数用于计算一个整数有多少位。我们需要计算它的原因是,我们需要在每一位上检查数字出现的情况。因此,我们需要计算出每一个数的位数,以便我们可以从最低位开始进行检查。
int dgt(int n) // 计算整数n有多少位
{
    int res = 0;
    while (n) ++ res, n /= 10;
    return res;
}

最后,我们可以看到主函数的完整代码。在每个测试用例中,我们计算在 a 和 b 之间数字 0~9 出现的次数,然后输出它们。

int main()
{
    int a, b;
    while (cin >> a >> b , a)
    {
        if (a > b) swap(a, b);
        for (int i = 0; i <= 9; ++ i) cout << cnt(b, i) - cnt(a - 1, i) << ' ';
        cout << endl;
    }
    return 0;
}

该算法的时间复杂度为 O(log10n)O(log10n),其中 n 是 a 和 b 中较大的那个数。空间复杂度为常数级别。

完整代码:

# include <iostream>
# include <cmath>
using namespace std;

int dgt(int n) // 计算整数n有多少位
{
    int res = 0;
    while (n) ++ res, n /= 10;
    return res;
}

int cnt(int n, int i) // 计算从1到n的整数中数字i出现多少次 
{
    int res = 0, d = dgt(n);
    for (int j = 1; j <= d; ++ j) // 从右到左第j位上数字i出现多少次
    {
        // l和r是第j位左边和右边的整数 (视频中的abc和efg); dj是第j位的数字
        int p = pow(10, j - 1), l = n / p / 10, r = n % p, dj = n / p % 10;
        // 计算第j位左边的整数小于l (视频中xxx = 000 ~ abc - 1)的情况
        if (i) res += l * p; 
        if (!i && l) res += (l - 1) * p; // 如果i = 0, 左边高位不能全为0(视频中xxx = 001 ~ abc - 1)
        // 计算第j位左边的整数等于l (视频中xxx = abc)的情况
        if ( (dj > i) && (i || l) ) res += p;
        if ( (dj == i) && (i || l) ) res += r + 1;
    }
    return res;
}

int main()
{
    int a, b;
    while (cin >> a >> b , a)
    {
        if (a > b) swap(a, b);
        for (int i = 0; i <= 9; ++ i) cout << cnt(b, i) - cnt(a - 1, i) << ' ';
        cout << endl;
    }
    return 0;
}

写在最后

最后,欢迎大家关注公众号“1号程序员”,回复“C100”可获得一份神秘技术资料!