【ACWing】338. 计数问题

题目地址:

https://www.acwing.com/problem/content/340/

给定两个正整数 a a a b b b,求 [ a , b ] [a,b] [a,b]之间所有数字中 0 ∼ 9 0\sim 9 09出现的次数。前导 0 0 0不许出现。

输入格式:
输入包含多组测试数据。每组测试数据占一行,包含两个整数 a a a b b b。当读入一行为0 0时,表示输入终止,且该行不作处理。

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

数据范围:
1 ≤ a , b < 1 0 8 1\le a, b< 10^8 1a,b<108
有多组测试数据

我们可以开个函数,专门计算 [ 1 , x ] [1,x] [1,x] 0 ∼ 9 0\sim 9 09出现的次数,设这个函数是 f ( x ) f(x) f(x),那么 [ a , b ] [a,b] [a,b]之间所有数字中 0 ∼ 9 0\sim 9 09出现的次数就是 f ( b ) − f ( a − 1 ) f(b)-f(a-1) f(b)f(a1)。所以只需要考虑怎么实现 f ( x ) f(x) f(x)这个函数。首先 f ( 0 ) = 0 f(0)=0 f(0)=0。接着考虑 x > 0 , f ( x ) x>0,f(x) x>0,f(x)。我们枚举 0 ∼ 9 0\sim 9 09在各个位上出现的时候的计数。比如,我们想枚举 k ∈ { 0 , 1 , . . . , 9 } k\in \{0,1,...,9\} k{ 0,1,...,9} x = a b c d e f g ‾ x=\overline{abcdefg} x=abcdefg这个数字的 d d d这个位置出现的情况下,有多少种可能。分两种情况讨论:
1、如果 k k k的左边是 0 ∼ a b c ‾ − 1 0\sim \overline{abc}-1 0abc1,那么 k k k右边可以任意取, 0 ∼ 999 0\sim 999 0999都可以,此时方案数就是 1000 a b c ‾ 1000\overline{abc} 1000abc
2、如果 k k k的左边是 a b c ‾ \overline{abc} abc,这个时候要看 k k k d d d的关系:
如果 k < d k<d k<d,那 k k k右边可以任意取,有 1000 1000 1000种情况;
如果 k = d k=d k=d,那 k k k右边只能取 0 ∼ e f g ‾ 0\sim \overline{efg} 0efg,有 1 + e f g ‾ 1+\overline{efg} 1+efg种情况;
如果 k > d k>d k>d,则没有可能, 0 0 0种情况;
但是对于 k = 0 k=0 k=0的情形需要另外考虑,因为不能有前导 0 0 0。首先枚举哪一位可以取 k k k的时候,要略过最高位(它本身就是前导 0 0 0了,要略过),接着,对于情况 1 1 1 k k k的左边的范围变成了 1 ∼ a b c ‾ − 1 1\sim \overline{abc}-1 1abc1。注意考虑这个情况就行。代码如下:

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

// 求10的x次方
int pow10(int x) {
    
    
    int res = 1;
    while (x--) res *= 10;
    return res;
}

// 将num表示的正整数
int get(vector<int> num, int l, int r) {
    
    
    int res = 0;
    for (int i = l; i >= r; i--)
        res = res * 10 + num[i];

    return res;
}

int count(int n, int x) {
    
    
    if (!n) return 0;
	
	// 逆序存储n的各个位
    vector<int> num;
    while (n) {
    
    
        num.push_back(n % 10);
        n /= 10;
    }

    n = num.size();

    int res = 0;
    // 从最高位开始枚举如果x是当前位的话有多少个可能性;如果x = 0的话要略过最高位
    for (int i = n - 1 - !x; i >= 0; i--) {
    
    
        if (i < n - 1) {
    
    
            res += get(num, n - 1, i + 1) * pow10(i);
            // 如果x = 0的话,其左边的范围要少1
            if (!x) res -= pow10(i);
        }

        if (num[i] == x) res += get(num, i - 1, 0) + 1;
        else if(num[i] > x) res += pow10(i);
    }
    
    return res;
}

int main() {
    
    
    int a, b;
    while (1) {
    
    
        cin >> a >> b;
        if (a == 0 && b == 0) break;

        if (a > b) swap(a, b);
        for (int i = 0; i < 10; i++)
            cout << count(b, i) - count(a - 1, i) << ' ';

        cout << endl;
    }

    return 0;
}

每次查询时间复杂度 O ( log ⁡ 10 max ⁡ { a , b } ) O(\log_{10}\max\{a,b\}) O(log10max{ a,b}),空间 O ( log ⁡ 10 max ⁡ { a , b } ) O(\log_{10}\max\{a,b\}) O(log10max{ a,b})

猜你喜欢

转载自blog.csdn.net/qq_46105170/article/details/114076205