问题描述:一本书的页码从自然数1 开始顺序编码直到自然数n。书的页码按照通常的习惯编排, 每个页码都不含多余的前导数字0。例如,第6 页用数字6 表示,而不是06 或006 等。数 字计数问题要求对给定书的总页码n,计算出书的全部页码中分别用到多少次数字0,1, 2,…,9。
数据输入:每个文件只有一行,给出表示书的总页码的整数n(1<=)
数据输出:输出文件共10行,在第k(k=1,2,3...10)行输出页码中用到数字k-1的次数
输入文件示例:11
输出文件示例:1 4 1 1 1 1 1 1 1 1
问题分析:
拿到题第一感觉是直接从1循环到n,然后计算每一位的数字计算总数
for(int i=1;i<=n;i++){ while(i/10){ total[i%10]++; i/=10; } }
可以看到,随着n的增大,程序的时间复杂度也在增大,所以方法不可取
然后我就用老师上课讲的方法试说一下吧
算法核心思想是考虑数字k在每个位上出现的次数然后加和
然后现在来找规律
拿9527举例吧,现在十位上是2,假设考虑十位上的数比2大,例如3,则十位上出现3的数字为
30-39 130-139....9430-9439 共(94+1)*10个
假设考虑十位上的数比2小,例如1,则十位上出现数字2的数字为
10-19 110-119....9510-9519 共(95+1)*10个
假设考虑十位上的数等于2,则十位上出现2的数字为
20-29 120-129....9420-9429 9520-9527 共(94+1)*10+(个位+1)
再用百位上的5举例,现在百位上是5,假设考虑百位上的数比5大,例如6,则百位上出现6的数字为
600-699 1600-1699....8600-8699 共(8+1)*100个
假设考虑百位上的数比5小,例如3,则百位上出现数字3的数字为
300-399 1300-1399....9300-9399 共(9+1)*100个
假设考虑百位上的数等于5,则百位上出现5的数字为
500-599 1500-1599....8500-8599 9500-9527 共(8+1)*100+(后两位+1)
然后假设当前位为cur,该位之前的数字为high,该位之后的数字为low,cur所在的位的权重为weight(十位为10,百位为100),则可以总结出的规律为:
k < cur时,k在该位出现的次数为(high+1)*weight
k > cur时,k在该位出现的次数为 high*weight
k = cur时,k在该位出现的次数为 high*weight+low+1
Note:
因为0前必须有数字,所以需要单独考虑0
看代码吧还是
#include<iostream>
#include<cstring>
using namespace std;
__int64 total[10];//记录每个数出现的个数
int main(){
int n;
__int64 low,cur,high;
int weight=1;//权值
cout<<"Enter the total page number n:";
cin>>n;
memset(total,0,sizeof(total));
while(n/weight){//将0单独考虑
low=n%weight;//当前位之后的数字
cur=(n/weight)%10;//当前位
high=n/(weight*10);//当前位之前的数字
if(!high){
weight*=10;
continue;
}
else if(cur==0){
total[0]+=((high-1)*weight+low+1);
}
else{
total[0]+=high*weight;
}
weight*=10;
}
weight=1;
while(n/weight){//1~9直接按照规律
low=n%weight;//当前位之后的数字
cur=(n/weight)%10;//当前位
high=n/(weight*10);//当前位之前的数字
for(int i=1;i<=9;i++){
if(i<cur){
total[i]+=(high+1)*weight;
}
else if(i>cur){
total[i]+=(high*weight);
}
else if(i==cur){
total[i]+=(high*weight+low+1);
}
}
weight*=10;
}
for(int i=0;i<=9;i++){
cout<<total[i]<<endl;
}
return 0;
}