c++版 如果急于寻找解决代码,请看【代码示例】
本文内容部分来源于《计算机算法设计与分析 第五版 》,作者:王晓东,出版社:电子工业出版社以及《计算机算法设计与分析习题解答 第五版 》,作者:王晓东,出版社:电子工业出版社。本文仅用于个人学习/研究/评论等非商业用途,如有侵权请联系删除。
问题描述:
一本书的页码从自然数1开始顺序编码直到自然数n。书的页码按照通常的习惯编排,每个页码不含多余的前导0.例如,第6页用数字6表示而不是06或006等,数字计数问题要求对给定书的总页码n,计算书的全部页码分别用到多少次数字0、1、……、9 。
算法设计:
给定表示书的总页码的十进制整数n(1<=n<=10^9),计算书的全部页码中分别用到多少次数字0、1、……、9 。
数据的输入和结果的输出:
输入文件示例input.txt
11
输出文件示例output.txt
0 1
1 4
2 1
3 1
4 1
5 1
6 1
7 1
8 1
9 1
分析与解答:
辅导书给出的:
考察0、1、……、9 组成的所有m位数。从m个0到m个9共有10^m个m位数。在这10^m个m位数中,0、1、……、9每个数字使用次数相同,设为f(m)。f(m)满足如下递归式:
由此可知,。据此,可从高位向低位进行统计,再减去多余的0的个数即可。
个人理解:
吐槽(不喜勿喷,实属情绪发泄):
说实话,不理解。不理解的点在于 1、为什么是从m个0到m个9共有10^m个m位数推导出来的?这个式子是怎么来的?(难道又是注意力惊人?)这不符合题意,算出来的0绝对多于真实数值。但为什么不细讲怎么将多余的0减去呢?我问ai,ai都崩了!让ai算,算出来50%左右的答案是错的!虽然将复杂问题寻找简单同类题得思路再对 复杂问题求解是常理,但这说的太简单的我看的稀里糊涂的。2、这个f(m)到底是哪个因变量?是一对多映射、一对一映射还是其他的?
从其他网站上找些资料,也不过都是标准答案的搬运工。我还是没有从贴子里找到我想知道的。
言归正传(完善办法):
提示信息综合:
依据教材配套解答的提示,可以明确知道三点:
1、需要使用递归式解决,
2、需要从高位向低位进行统计,
3、递归式并不完美,正确答案需要完善
根据ai在崩前给出的提示信息,大概可以知道三点:
1、这个公式主要的运用方式应该不是将递归式直接带进去算,并且可能不用递归式形式,而是用递归式化简后的式子,即,
2、这个式子大概是指的每个数字在第m位上的次数。(非数学科班,但这么写能让自己明白点,所以数学佬可能会觉得以下公式多余且有错漏,其余同学可能瞅见会觉得脑袋疼。不熟悉csdn上的公式输入,可能会显得乱糟糟)
(总页码的数)即设总页码n是最高位为i,最高位上的数为(
≠0 and i>0)的十进制整数,设t为整数且t>=1且t<=i。令个位(权为10^0)为t=1,个位上的数为
,以此类推,则n可表示为
。
(使用到的数字,不是总页码)令0,1,2,……,9中每一个数字在第t位上的重复的次数为,x=0,1,2,……,9,即n=11,i=2的情况下,数字5在1到11的(包含端点)整数范围内,在个位上重复的次数是
=1,在十位上重复的次数是
=0,那么数字5在1到11的范围内重复的总次数为
。使用递归化简式后,
,C为在
或增加或减少的量。
3、使用的时候需要分类使用。可能需要分成x>、x=
、x<
的情况。
探索分类可能情况:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |
00-09 | 10+1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
10-19 | 11+1 | 1+(10+1) | 1+1 | 1+1 | 1+1 | 1+1 | 1+1 | 1+1 | 1+1 | 1+1 |
20-29 | 12+1 | 12+1 | 2+(10+1) | 2+1 | 2+1 | 2+1 | 2+1 | 2+1 | 2+1 | 2+1 |
30-39 | 13+1 | 13+1 | 13+1 | 3+(10+1) | 3+1 | 3+1 | 3+1 | 3+1 | 3+1 | 3+1 |
40-49 | 14+1 | 14+1 | 14+1 | 14+1 | 4+(10+1) | 4+1 | 4+1 | 4+1 | 4+1 | 4+1 |
50-59 | 15+1 | 15+1 | 15+1 | 15+1 | 15+1 | 5+(10+1) | 5+1 | 5+1 | 5+1 | 5+1 |
60-69 | 16+1 | 16+1 | 16+1 | 16+1 | 16+1 | 16+1 | 6+(10+1) | 6+1 | 6+1 | 6+1 |
70-79 | 17+1 | 17+1 | 17+1 | 17+1 | 17+1 | 17+1 | 17+1 | 7+(10+1) | 7+1 | 7+1 |
80-89 | 18+1 | 18+1 | 18+1 | 18+1 | 18+1 | 18+1 | 18+1 | 18+1 | 8+(10+1) | 8+1 |
90-99 | 19+1 | 19+1 | 19+1 | 19+1 | 19+1 | 19+1 | 19+1 | 19+1 | 19+1 | 9+(10+1) |
从上图中可以判断,x>为一类,x=
、x<
为一类。但n中的
并不总是全部为9,那么,对公式进行修正。有n=
,则有A=n/(10^(i-1))=
个完整的可以使用公式
的整体,暂记为
,令B=n%(10^(i-1)),i位上则有
,
记录已经算好的和
,更新n=B,A=n/(10^(i-1)),B=n%(10^(i-1)),则有
和
,当t=1时,
不计算,
,计算完成后累加并退出循环。此时就能求出答案了。
综上,思路大致制定如下:每次输入n后,从n的最高位开始分情况使用,将得出的
存入并累加,减去多余的0后,得出结果。
存储设计:
1、n的数据类型:n的长度为1到10^9,需根据实际情况选择合适的数据类型。(int在不同的机型或不同的编程语言不一定会有相同的长度,注意)。选择c语言中的int(4字节*8位)。
2、辅助空间:在程序中,必须存在存储的空间,最大估计
应该是10^17,这个数超过了int的范围。使用double[10]。
算法设计:
在上述过程中,我们已经求出了大致思路。但有一个问题仍需解决,那就是有多少个不必要的0 。
根据题目,不必要的0有两类,一是整数0,二是前导0。
当n=,在公式
,包含上述两类0,但无法通过一个式子求出;在公式
中,有
个i位上的0 。在公式
中,虽然公式中不含两类0,公式
也不含两类0,公式
中也不含两类0。由此可知,除了最高位的公式明确含前导0并且能求出最高为上的前导0之外,其余的前导0和整数0都包含在
中。将含前导0的数字列出后发现,前导0和整数0的个数貌似和其所在的数位有关。
。
并且通过,
、
、
公式的相似程度、条件的限制情况、使用情况的类似,可以将i和t的公式分别合并。
综上,思路大致制定如下:每次输入n后,从n的最高位开始分情况使用,将得出的
存入并累加,减去多余的0后,得出结果。
思路组合:
统计数字个数算法
算法执行前系统状态:存在input.txt文件
算法执行后系统状态:增加output.txt文件
算法的目标:通过读取input.txt文件获取输入(总页码数),执行统计数字个数的操作,并将结果生成output.txt文件
算法的输入:总页码数n
算法的输出:10个数字及其重复的次数,是两组数据
执行过程:
T[x]:数组T存储下标为x时x重复的次数,初值为0
t:执行过程中不超过最高位、不低于个位的数位,初值为i+1
A:第t位上的数字Mt,初值为Mi=n/10^(t-1)
B:从个位到t-1位的数,初值为n%10^(t-1);
while(--t>1) //从最高位到十位的循环,不处理个位
{
for(x=0;x<10;x++)
{
T[x]=T[x]+A*(t-1)*pow(10,t-2);//实现f1
if(x<A) T[x]=T[x]+pow(10,t-1);//实现f2
if(x==A) T[x]=T[x]+B+1;
}
T[0]=T[0]-pow(10,t-1);//处理前导0
}
for(x=0;x<10;x++) //处理个位
{
if(x<=A) T[x]=T[x]+1;//实现f2
else break;
}
T[0]=T[0]-1;//处理整数0
代码示例:
/*running programme*/
#include <iostream>
#include<fstream>
#include<cmath>
#include<iomanip>
using namespace std;
double T[10];
int main()
{
int n,A,B,t,x;
//open input.txt for n
ifstream inputFile;
ofstream outputFile;
inputFile.open("input.txt",ios::in);
if(!inputFile.is_open())
{
cout<<"input.txt opens failed!";
return 1;
}
inputFile>>B;
inputFile.close();
//ready of t
for(int i=1;i<=10;i++)
if(B/(int)(pow(10,i-1))==0)
{
t=i;break;
}
//algorithm body
while(--t>1) //从最高位到十位的循环,不处理个位
{
n=B;
A=n/(int)(pow(10,t-1));
B=n%(int)(pow(10,t-1));
for(x=0;x<10;x++)
{
T[x]=T[x]+A*(t-1)*pow(10,t-2);//实现f1
if(x<A) T[x]=T[x]+pow(10,t-1);//实现f2
if(x==A) T[x]=T[x]+B+1;
}
T[0]=T[0]-pow(10,t-1);//处理前导0
}
for(x=0;x<10;x++) //处理个位
{
if(x<=A) T[x]=T[x]+1;//实现f2
else break;
}
T[0]=T[0]-1;//处理整数0
//create output.txt
outputFile.open("output.txt",ios::trunc);
for(x=0;x<10;x++) //处理个位
{
outputFile<<x<<" ";
outputFile<< std::fixed << std::setprecision(0) << T[x] <<"\n";
}
outputFile.close();
return 0;
}
运算结果正确。