【问题描述】
给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次。
【输入格式】count.in
输入文件中仅包含一行两个整数a、b,含义如上所述。
【输出格式】count.out
输出文件中包含一行10个整数,分别表示0-9在[a,b]中出现了多少次。
【输入样例】
1 99
【输出样例】
9 20 20 20 20 20 20 20 20 20
【数据规模】
30%的数据中,a<=b<=106;
100%的数据中,a<=b<=1012。
朴素的方法就不再多说了,就是枚举区间然后直接累加即可。
当然朴素算法显然要超时。
通过观察可以发现,对于所有i位数,数字1到9出现的次数为i·10^(i-1);数字0出现的次数为i·10^(i-1)(包含前导0)/或9i·10^(i-2)(不包含前导0),那么统计起来就变得非常简单了。
详细统计过程见程序注释。
Accode:
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <string>
typedef long long int64;
int64 x10[20] = {0, 1};
int64 cnt[10];
int64 a, b;
char A[20], B[20];
int la, lb;
inline int64 getint()
{
int64 res = 0; char tmp;
while (!isdigit(tmp = getchar()));
do res = (res << 3) + (res << 1) + tmp - '0';
while (isdigit(tmp = getchar()));
return res;
}
int main()
{
freopen("count.in", "r", stdin);
freopen("count.out", "w", stdout);
a = getint(); ++(b = getint());
for (int i = 1; x10[i] <= b; ++i)
x10[i + 1] = (x10[i] << 3) + (x10[i] << 1);
sprintf(A, "%I64d", a); sprintf(B, "%I64d", b);
//将a、b转换为字符串。
strrev(A); strrev(B);
//倒转字符串A、B。
--(la = strlen(A)); --(lb = strlen(B));
//得到两字符串的长度。
for (int i = 1; i < lb + 1; ++i)
cnt[0] += x10[i - 1] * (i - 1) * 9;
//统计低位数中出现的0。
for (int j = 1; j < 10; ++j)
cnt[j] += x10[lb] * lb;
//统计低位数中出现的1到9。
for (int i = lb; i > -1; --i)
{
for (int j = i == lb;
//当为最高位的时候从1开始枚举,否则从0开始枚举。
j < B[i] - '0'; ++j)
{
cnt[j] += x10[i + 1];
//最后(i-1)位以j开头的数有10^i个。
for (int k = 0; k < 10; ++k)
cnt[k] += x10[i] * i;
//低位数字自由取数,直接累加。
}
cnt[B[i] - '0'] += b % x10[i + 1];
//高位数字还可以取这么多个。
}
for (int i = 1; i < la + 1; ++i)
cnt[0] -= x10[i - 1] * (i - 1) * 9;
for (int j = 1; j < 10; ++j)
cnt[j] -= x10[la] * la;
for (int i = la; i > -1; --i)
{
for (int j = i == la;
j < A[i] - '0'; ++j)
{
cnt[j] -= x10[i + 1];
for (int k = 0; k < 10; ++k)
cnt[k] -= x10[i] * i;
}
cnt[A[i] - '0'] -= a % x10[i + 1];
}
for (int j = 0; j < 10; ++j)
printf("%I64d ", cnt[j]);
return 0;
}