找到 [a,b] 中出现 x 的次数

设定一个函数 count(n, x) 1 ~ n 中出现 x 的次数 则 count(b, x) - count(a - 1, x) 就是 a~b 中出现x的次数

常用技巧,求一个区间不好算,可以求从头到 b, 再求从头到 a, 最后作差即可

求1~n中 出现 x 的次数

暴力法,for(i, n) for(j, get(i)) if(is(x)) res++ 渐进时间复杂度为 O(n * 8) n ~~ 1e8,必超时 需要优化

对于有关大整数的问题,最好是与其位数相关(比如高精度就是以位数处理) 同理,我们把 第 1 ~ get(n) 位中 是 x 的情况求和即可 就像汉明码中检测位分组那样。

外层循环为 for(i, get(n)) 如何求某一位为x的次数和呢? 设给的n为 abc d efg 一般的来说,符合条件的数为 xxxdxxx,需要找出是这个格式的数量。 类似于石子合并的区间问题,我们可以拆分成两个部分,左侧从 000 ~ abc - 1,那么右侧从 000 ~ 999 都符合条件,即 abc - 1 * 1000 个。 d左边固定,之前算的是 001 ~ abc - 1,故固定为 abc, 则右边就是 efg 个。 可得 res += abc - 1 * 10 ^ (get(n) - j) + efg 为第 j 位的包含 d 的数,但需要求的是包含 x 的数, 左侧为 001 ~ abc - 1 时,当前位可以选 0 ~ 9,故不需要管当前位是否 >= x。 当左侧为 abc 时,当前位若小于 x , 则不符合条件。 若等于x,则 res += efg + 1; 若大于x,则 右侧可取 0 ~ 999, 为 10 ^ (get(n) - j)。 即:

int len = get(n), res = 0;
for(int j = 1; j <= len; i++)
{
	int p = pow(10, len - j);//当前位的进制
	int l_num = n / p / 10; // abc
	int r_num = n % p; // efg
	int i = (n / p) % 10; // 第j位的数
	res += l_num * p;
	if(i > x) res += p;
	else if(i == x)res += r_num + 1; // 加一因为有0
}
 

看起来挺棒对吧,但少考虑了所有数字枚举都要考虑的问题,前导0。 假如说 x == 0,左边 000 ~ abc - 1 为000时,整个数并不一定包含0,为 efg,也就是说加重复了一部分,这之中若为 100,它的0应该在 j == len - 1, len 时计算并加上,这里若加上则属于重复计算。 对于计数问题,不能出现重复计算,对于重复的部分,通常有两种解决办法,不加上重复的 和 加上重复之后减去重复的部分。 不加上:

if(x) res += l_num * p;
else if(l_num)res += (l_num - 1) * p; // 不加000的情况
// 因为有 l_num - 1,那么若左边为0,也不应该加

减去重复的: 把第一条下面加一条:if(x == 0) res -= r_num + 1;

左边的处理好了,对于右边来说,x == 0,始终会进行res += p 或 res += r_num + 1 的运算,正常该数为 abc0efg,符合条件,但若 l_num == 0 呢? 依然是 efg,直接不让它加上更简洁一些:

if(i > x  && !(x == 0 && l_num == 0)) res += p;
else if(i == x && !(x == 0 && l_num == 0)) res += r_num + 1;
// 或者条件换为 (x != 0 || l_num != 0) 即 (x || l_num)

现在便可得到正确结果了。

最终代码为:

#include <iostream>
#include <cstring>
#include <math.h>
#include <algorithm>
using namespace std;
 
 
int get(int n)
{
    int res = 0;
    while(n)
    {
        res++;
        n /= 10;
    }
    return res;
}
 
 
int count(int n, int x)
{
    int len = get(n), res = 0;
    for(int j = 1; j <= len; j++)
    {
        int p = pow(10, len - j), l_num = n / p / 10, r_num = n % p, i = (n / p) % 10;
        if(x) res += l_num * p;
        else if(l_num) res += (l_num - 1) * p;
        
        if(i > x && !(x == 0 && l_num == 0)) res += p;
        else if(i == x && ! (x == 0 && l_num == 0)) res += r_num + 1;
    }
    return res;
}
 
 
int main()
{
    int a,b;
    while(cin >> a >> b && a + b)
    {
        if(a > b) swap(a,b);
        for(int i = 0; i <= 9; i++)
        {
            cout << count(b,i) - count(a - 1,i) << " ";
        }
        cout << endl;
    }
    return 0;
}