题解:AcWing 338 计数问题

【题目来源】

AcWing:338. 计数问题 - AcWing题库

【题目描述】

给定两个整数 \(a\)\(b\),求 \(a\)\(b\) 之间的所有数字中 \(0\sim9\) 的出现次数。

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

1024 1025 1026 1027 1028 1029 1030 1031 1032

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

【输入】

输入包含多组测试数据。

每组测试数据占一行,包含两个整数 \(a\)\(b\)

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

【输出】

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

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

【输入样例】

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

【输出样例】

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

【解题思路】

image

image

image

image

image

【算法标签】

《AcWing 338 计数问题》 #动态规划# #数位统计DP#

【代码详解】

// 引入所有标准库头文件,方便使用各种标准库功能
#include <bits/stdc++.h>

// 使用标准命名空间,避免每次使用标准库函数时都需要加 std:: 前缀
using namespace std;

// 定义常量
const int N = 10;  // 定义常量N,值为10,可能用于限制数字范围或其他用途

// 函数:将数组num中从索引l到r的数字组合成一个整数
// 参数:
//   num: 存储数字的数组,个位在num[0],高位在num[r]等
//   l: 数组的高位起始索引
//   r: 数组的低位结束索引
// 返回值:组合后的整数
int get(vector<int> num, int l, int r)  
{
    int res = 0;  // 初始化结果为0
    // 从高位l到低位r,逐位构建整数
    for (int i = l; i >= r; i--) 
        res = res * 10 + num[i];  // 将当前位数字加入结果
    return res;  // 返回组合后的整数
}

// 函数:计算10的x次方
// 参数:
//   x: 幂次
// 返回值:10的x次方的结果
int power10(int x)  
{
    int res = 1;  // 初始化结果为1
    // 循环x次,每次乘以10
    while (x--) 
        res *= 10;
    return res;  // 返回10的x次方
}

// 函数:计算1到n中数字x出现的次数
// 参数:
//   n: 上限整数
//   x: 要统计的数字(0-9)
// 返回值:数字x在1到n中出现的总次数
int count(int n, int x)  
{
    if (n == 0) 
        return 0;  // 如果n为0,返回0,因为0中没有数字x(除非x=0,但此函数统计1到n)

    vector<int> num;  // 定义一个数组,用于存储整数n的每一位数字,个位在num[0]
    
    // 将整数n的每一位数字分解并存储到数组num中,个位在num[0],高位在num[num.size()-1]
    while (n > 0) 
    {
        num.push_back(n % 10);  // 取当前位的数字
        n /= 10;  // 去掉当前位
    }

    int len = num.size();  // 获取整数n的位数
    int res = 0;  // 初始化结果为0

    // 统计数字x在整数n的每一位(i位)上,在1到n中出现的次数
    // 当x=0时,整数n的最高位不能为0,因此i从len-1 - !x开始
    for (int i = len - 1 - !x; i >= 0; i--) 
    {
        if (i < len - 1)  // 当前位不是最高位
        {
            // 计算当前位左侧的数字组合数,并乘以10的i次方(即右侧可能的数字组合数)
            res += get(num, len - 1, i + 1) * power10(i);
            
            // 如果x为0,需要排除左侧全为0的情况(即000...)
            if (x == 0) 
                res -= power10(i);  // 排除左侧全为0的组合
        }

        // 当前位与原数相同样式:abcXyyy
        if (num[i] == x) 
            res += get(num, i - 1, 0) + 1;  // 当前位等于x,加上当前位及低位的具体组合数并加1
        else if (num[i] > x) 
            res += power10(i);  // 当前位大于x,所有右侧可能的组合都有效
    }

    return res;  // 返回数字x在1到n中出现的总次数
}

// 主函数,程序的入口点
int main()
{
    int a, b;  // 定义两个整数a和b,表示统计的范围[a, b]

    // 循环读取输入的a和b,直到输入为0 0时结束
    while (cin >> a >> b) 
    {
        if (a == 0 && b == 0) 
            break;  // 如果a和b都为0,结束循环

        if (a > b) 
            swap(a, b);  // 确保a <= b,a为小数,b为大数

        // 遍历数字0到9,统计每个数字在范围[a, b]中出现的次数
        for (int i = 0; i <= 9; i++) 
            // 输出数字i在范围[a, b]中出现的次数,即count(b, i) - count(a-1, i)
            cout << count(b, i) - count(a - 1, i) << " ";

        cout << endl;  // 换行,准备下一轮输入
    }

    return 0;  // 程序正常结束,返回0
}

【运行结果】

1 10
1 2 1 1 1 1 1 1 1 1
44 497
85 185 185 185 190 96 96 96 95 93
346 542
40 40 40 93 136 82 40 40 40 40
1199 1748
115 666 215 215 214 205 205 154 105 106
1496 1403
16 113 19 20 114 20 20 19 19 16
1004 503
107 105 100 101 101 197 200 200 200 200
1714 190
413 1133 503 503 503 502 502 417 402 412
1317 854
196 512 186 104 87 93 97 97 142 196
1976 494
398 1375 398 398 405 499 499 495 488 471 
1001 1960
294 1256 296 296 296 296 287 286 286 247
0 0
posted @ 2026-02-26 11:45  团爸讲算法  阅读(0)  评论(0)    收藏  举报