问题描述
一本书的页码从自然数1开始顺序编码直到自然数n。书的页码按照通常习惯编排,每个页码都不含多余的前导数字0。数字计数问题要求对给定书的总页码n,计算书的全部页码分别用到多少次数字0,1,2,…,9。
- 给定表示书的总页码的十进制整数n,计算书的全部页码中分别用到多少次数字0,1,2,…,9。
- 输入数据由文件名为input.txt的文本文件提供。每个文件只有1件,给出表示书的总页码的整数n。
- 将计算结果输出到文件output.txt。输出文件共10行,在第k(k=1,2,…,10)行输出页码中用到数字k-1的次数。
算法设计
从n个0到n个9共有10n个n位数。在这10n个n位数中,0,1,2,…,9每个数字使用次数相同,设为f(n)。f(n)满足如下递归式:
由此可知,f(n)=n10n-1,再从高位向低位进行统计,减去多余的0的个数即可。
运行结果
在文本input.txt中输入页码36,运行程序。
运行程序后,打开output.txt文本,可知计算结果共10行,分别显示了用到数字0,1,2,…,9的次数。
源代码
#include <iostream>
#include<fstream>
#include<cmath>
using namespace std;
int Num[10];
//得到区间的各数字的出现次数
int getPeriodNum(int upside) {
if (upside == 0)
return 0;
int len = log10(upside) + 1;//位数
if (len == 1)//0~9
return 1;
else
return pow(10, len - 1) + 10 * static_cast<__int64>(getPeriodNum(upside % 10));
}
void getNum(int number) {
int len = log10(number) + 1; //位数
if (len == 1) {//一位数
for (int i = 0; i <= number; i++)
Num[i]++;
return;
}
//区间数
int parts = number / ((int)pow(10, len - 1));
//区间上界
int upside = 0;
for (int i = 0; i <= len - 2; i++)
upside += 9 * pow(10, i);
//每个区间的各位数字的个数(不包括最高位)
int PeriodNum = getPeriodNum(upside);
for (int i = 0; i < 10; i++)
Num[i] += parts * PeriodNum;
//最高位
for (int i = 0; i <= parts - 1; i++)
Num[i] += pow(10, len - 1);
int x = ceil(pow(10, len - 1));
int rest = number % x;//余数
Num[parts] += rest + 1;
//余数为0
if (rest == 0) {
Num[0] += pow(10, len - 1);
return;
}
//递归处理余数
int lenRest = log10(rest) + 1;
if (lenRest < len - 1)
Num[0] += (len - 1 - lenRest) * (rest + 1);
//处理余数
getNum(rest);
}
int main() {
ifstream in;
in.open("input.txt");
int number;
in >> number;
in.close();
ofstream out("output.txt");
//初始化
for (int i = 0; i < 10; i++) {
Num[i] = 0;
}
getNum(number);
//处理零
int len = log10(number) + 1;
for (int i = 0; i <= len - 1; i++)
Num[0] -= pow(10, i);
for (int i = 0; i < 10; i++)
out << Num[i] << '\n';
out.close();
return 0;
}