问题描述:
一本书的页码从自然数 1 开始顺序编码直到自然数 n。书的页码按照通常的习惯编排,每个页码都不含多余的前导数字 0。例如,第 6 页用数字 6 表示,而不是 06 或 006 等。数字计数问题要求对给定书的总页码 n,计算出书的全部页码中分别用到多少次数字 0,1,2,…,9。编程任务
给定表示书的总页码的 10 进制整数 n (1≤n≤10^9) 。编程计算书的全部页码中分别用到多少次数字 0,1,2,…,9。数据输入:
输入数据由文件名为 input.txt 的文本文件提供。 每个文件只有 1 行,给出表示书的总页码的整数 n。结果输出:
程序运行结束时,将计算结果输出到文件 output.txt 中。输出文件共有 10 行,在第 k 行 输出页码中用到数字 k-1 的次数,k=1,2,…10。思考:
这道题很容易想到暴力解法,比如把各个数进行取余运算再进行数字匹配,或者把所有的数字组成字符串再进行遍历和匹配计数,但这样的作法只能应付数据较小的情况,题目的数据上限是10^9,所以这些方法都不适宜。查阅一些资料后了解到这样一个公式:
n位数的各个数字出现的次数满足:n*10^(n-1)。
这里的“n位数”是指用0补位的,例如三位数是值 000 - 999。
我们可以把一个n+1位数拆分为一个首位数(m,m肯定不是0) 和一个 n位数(k)。这个k定然满足上述 "n位数" 的情况。
例如:给定页数1315,我们可以把它拆分为 首位8和一个3位数。
在有首位数(m)的前提下,上面的公式就发生了一个小的变化,这个很好理解,假设我们给定数据3556,m=3,共有如下3种有效情况:
1.首位数为1,1连接3位数。
2.首位数为2,2连接3位数。
3.首位数为3,3连接3位数。
那么上面的公式就变成这样:一个n+1位数,在去除首位数(m)的情况下,后面n位数出现0-9的次数均为 m*n*10^(n-1)。
到这里我们已经完成了第一步,然后再考虑首位数的情况,首位数出现的次数与后面的数有关,这个很好理解。
至此,一个n+1位数的情况已经分析得差不多了。我们还得考虑一些特殊情况,比如1000,1003这些数,我们对数去首位的方法采用取余法,这样会过滤掉中间或者尾部的0,针对这类情况我们直接将其加上就行。
下面根据分析情况得出代码:
/*
统计数字问题
*/
#include <string>
#include <iostream>
#include <fstream>
#include <ostream>
#include <stdlib.h>
#include <vector>
#include <ctime>
#include <cmath>
using namespace std;
void func(unsigned long num, int size, vector<unsigned long> & count)
{
int first_num = num / int(pow(10, size - 1)); //首位数
unsigned long after_fst_num = num % int(pow(10, size - 1)); //去除首位数得余数
int size_after_fst_num = to_string(after_fst_num).size(); //余数位数 //余数位数
//去除首位,补0计数
for (int i=0; i<10; i++)
{
count[i] += unsigned long(first_num * (size - 1) * pow(10, size - 2));
}
//首位从0到first_num计数
count[first_num] += after_fst_num + 1; // +1是因为多了个了全零位
for (int i = 0; i < first_num; i++)
{
count[i] += unsigned long(pow(10, size - 1));
}
if (after_fst_num == 0)
{
if (size - 1 > 0)
count[0] += size - 1; //加上尾巴的0
return;
}
else if (size - size_after_fst_num > 1) //加上中间的0,因为每次递归都是用的余数,中级的0会被跳过
{
count[0] += unsigned long( (size - size_after_fst_num)*after_fst_num );
}
func(after_fst_num, size_after_fst_num, count);
}
int main()
{
//time_t t1 = clock(); //测试运行时间
//读取测试文件数据--页数
string _c; //存放页数
fstream f("input.txt");
getline(f, _c);
f.close();
int size = _c.size(); //size : 输入数据的位数
unsigned long num = atoi(_c.c_str()); //num : 测试数据
vector<unsigned long> count;
for (int i = 0; i < 10; i++)
{
count.push_back(0); //初始化数组
}
func(num, size, count);
//减去前置多填充的0,位数从1开始
for (int i = 1; i <= size; i++)
{
count[0] -= unsigned long(pow(10, i - 1));
}
//输出到屏幕
for (auto x : count)
{
cout << x << endl;
}
//输出到文件
ofstream outfile;
outfile.open("outfile.txt");
for (auto x : count)
{
outfile << x << endl;
}
outfile.close();
//记录测试时间
//time_t t2 = clock();
//cout << "运行时间: " << t2 - t1 << " ms" << endl;
return 0;
}