【计算机算法设计与分析 第五版 王晓东】1-1统计数字问题

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)满足如下递归式:

 f(m)=\left\{\begin{matrix} 10f(m-1)+10^{m-1} & m>1 & \\ 1 & m=1 & \end{matrix}\right.

由此可知,f(m)=m10^{m-1}。据此,可从高位向低位进行统计,再减去多余的0的个数即可。

个人理解:

吐槽(不喜勿喷,实属情绪发泄):

说实话,不理解。不理解的点在于 1、为什么是从m个0到m个9共有10^m个m位数推导出来的?这个式子是怎么来的?(难道又是注意力惊人?)这不符合题意,算出来的0绝对多于真实数值。但为什么不细讲怎么将多余的0减去呢?我问ai,ai都崩了!让ai算,算出来50%左右的答案是错的!虽然将复杂问题寻找简单同类题得思路再对 复杂问题求解是常理,但这说的太简单的我看的稀里糊涂的。2、这个f(m)到底是哪个因变量?是一对多映射、一对一映射还是其他的?

从其他网站上找些资料,也不过都是标准答案的搬运工。我还是没有从贴子里找到我想知道的。

言归正传(完善办法):

提示信息综合:

依据教材配套解答的提示,可以明确知道三点:

        1、需要使用递归式解决,

        2、需要从高位向低位进行统计,

        3、递归式并不完美,正确答案需要完善

根据ai在崩前给出的提示信息,大概可以知道三点:

        1、这个公式主要的运用方式应该不是将递归式直接带进去算,并且可能不用递归式形式,而是用递归式化简后的式子,即f(m)=m10^{m-1}

        2、这个式子大概是指的每个数字在第m位上的次数。(非数学科班,但这么写能让自己明白点,所以数学佬可能会觉得以下公式多余且有错漏,其余同学可能瞅见会觉得脑袋疼。不熟悉csdn上的公式输入,可能会显得乱糟糟)

(总页码的数)即设总页码n是最高位为i,最高位上的数为M_{i}M_{i}≠0 and i>0)的十进制整数,设t为整数且t>=1且t<=i。令个位(权为10^0)为t=1,个位上的数为M_{1},以此类推,则n可表示为\sum_{t=1}^{i}M_{t}10^{t-1}

(使用到的数字,不是总页码)令0,1,2,……,9中每一个数字在第t位上的重复的次数为T_{x}(t),x=0,1,2,……,9,即n=11,i=2的情况下,数字5在1到11的(包含端点)整数范围内,在个位上重复的次数是T_{5}(1)=1,在十位上重复的次数是T_{5}(2)=0,那么数字5在1到11的范围内重复的总次数为T_{5}=T_{5}(1)+T_{5}(2)=1。使用递归化简式后,T_{x}=f(t)+C,C为在f(t)或增加或减少的量。

3、使用的时候需要分类使用。可能需要分成x>M_{t}、x=M_{t}、x<M_{t}的情况。

探索分类可能情况:

探索f(t)
0123456789
00-0910+1111111111
10-1911+11+(10+1)1+11+11+11+11+11+11+11+1
20-2912+112+12+(10+1)2+12+12+12+12+12+12+1
30-3913+113+113+13+(10+1)3+13+13+13+13+13+1
40-4914+114+114+114+14+(10+1)4+14+14+14+14+1
50-5915+115+115+115+115+15+(10+1)5+15+15+15+1
60-6916+116+116+116+116+116+16+(10+1)6+16+16+1
70-7917+117+117+117+117+117+117+17+(10+1)7+17+1
80-8918+118+118+118+118+118+118+118+18+(10+1)8+1
90-9919+119+119+119+119+119+119+119+119+19+(10+1)

从上图中可以判断,x>M_{t}为一类,x=M_{t}、x<M_{t}为一类。但n中的M_{t}并不总是全部为9,那么,对公式进行修正。有n=\sum_{t=1}^{i}M_{t}10^{t-1},则有A=n/(10^(i-1))=M_{i}个完整的可以使用公式f(t)的整体,暂记为f_{1}(i)=A(i-1)10^{i-2},令B=n%(10^(i-1)),i位上则有f_{2}(i)=\left\{\begin{matrix} 10^{i-1} & x<M_{i} & \\ B+1& x=M_{i} & \\ 0& x>M_{i} & \end{matrix}\right.,

记录已经算好的f_{1}(i)f_{2}(i),更新n=B,A=n/(10^(i-1)),B=n%(10^(i-1)),则有f_{1}(t)=A(t-1)10^{t-2}f_{2}(t)=\left\{\begin{matrix} 10^{t-1} & x<M_{t} & \\ B+1& x=M_{t} & \\ 0& x>M_{t} & \end{matrix}\right.,当t=1时,f_{1}(i)不计算,f_{2}(1)=\left\{\begin{matrix} 1 & x<=M_{1} \\ 0& x>M_{1} & \end{matrix}\right.,计算完成后累加并退出循环。此时就能求出答案了。

综上,思路大致制定如下:每次输入n后,从n的最高位开始分情况使用f(t),将得出的T_{x}(t)=f(t)存入并累加,减去多余的0后,得出结果。

存储设计:

1、n的数据类型:n的长度为1到10^9,需根据实际情况选择合适的数据类型。(int在不同的机型或不同的编程语言不一定会有相同的长度,注意)。选择c语言中的int(4字节*8位)。

2、辅助空间:在程序中,必须存在存储T_{x}(t)的空间,最大估计T_{x}应该是10^17,这个数超过了int的范围。使用double[10]。

算法设计:

在上述过程中,我们已经求出了大致思路。但有一个问题仍需解决,那就是有多少个不必要的0 。

根据题目,不必要的0有两类,一是整数0,二是前导0。

当n=\sum_{t=1}^{i}M_{t}10^{t-1},在公式f_{1}(i)=A(i-1)10^{i-2},包含上述两类0,但无法通过一个式子求出;在公式f_{2}(i)=\left\{\begin{matrix} 10^{i-1} & x<M_{i} & \\ B+1& x=M_{i} & \\ 0& x>M_{i} & \end{matrix}\right.中,有10^{i-1}个i位上的0 。在公式f_{1}(t)=A(t-1)10^{t-2}中,虽然公式中不含两类0,公式f_{2}(t)=\left\{\begin{matrix} 10^{t-1} & x<M_{t} & \\ B+1& x=M_{t} & \\ 0& x>M_{t} & \end{matrix}\right.也不含两类0,公式f_{2}(1)=\left\{\begin{matrix} 1 & x<=M_{1} \\ 0& x>M_{1} & \end{matrix}\right.中也不含两类0。由此可知,除了最高位的公式明确含前导0并且能求出最高为上的前导0之外,其余的前导0和整数0都包含在f_{1}(i)中。将含前导0的数字列出后发现,前导0和整数0的个数貌似和其所在的数位有关。T_{shanchude0}=\sum ^{i} _{t=1}10^{t-1}=\frac{10^{i}-1}{9}

并且通过f_{1}(i)f_{1}(t)f_{2}(i)f_{2}(t)公式的相似程度、条件的限制情况、使用情况的类似,可以将i和t的公式分别合并。

综上,思路大致制定如下:每次输入n后,从n的最高位开始分情况使用f(t),将得出的T_{x}(t)=f(t)存入并累加,减去多余的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;
}

运算结果正确。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值