题目来源:51nod1009
题目大意:输入一个十进制数N,计算出从0到N所有数里所有1的个数(注:111里有3个1)
【冷静分析】这道题乍一看有些棘手(实际也不是水题),我们不妨先找找规律。
·一位数里一共有几个1?
答:1。一个。
·两位数里有几个1?
答:个位数是1的:1,11,21,31,……(10个)
十位数是1的:10,11,12,13……(10个)
所以有二十个。
·三位数呢?
答:我不想列举了,有三百个。
等等:1,20,300……
……??????!似乎真的有规律啊!
也就是说,从0——9999(k个9)一共有1的个数是:
f(k)=k(10^k)————①*
惊人的发现啊!我们继续找规律。
不妨以12345这个数为例:
由结论①可知,在1——9999内有4000个1
接下来看10000——12345
我们惊奇地发现,这2346个数的万位都是1,所以答案加上2346
顺着这个思路,我们再来看千位:
当千位为0时,后面的三个数无论是多少,整体都不会超过12345
我们自然想到结论①,这0——999中有300个1
当千位为1时,后面三个数无论是多少,整体也不会超过12345
再加上300,当然,不能忘了千位的1,它一共贡献了1000
当千位为2时,抱歉,路不通了
那我们再看百位。同理:
百位可以固定的数是0,1,2,它们总共会贡献3 * f(2)= 60
百位上的1可以贡献: 100
看十位:固定0,1,2,3,贡献4 * f(1)= 4
十位上的1可以贡献: 10
个位可以是0,1,2,3,4,5,贡献: 1
所以答案是8121
怎么设计程序呢?
首先要注意到的是函数f,可以预处理一下,用数组保存每一个k的f[k]
剩下来的操作虽然冗长,但并不繁杂,涉及到的操作如下:
1、若正在处理的数字是1,计算出1后面的数字m,ans+=m+1(回想加2346的操作)
2、若处理的是个位,若个位不是0,则ans++
3、若正在处理的数字c不是个位也不是1,从0枚举到c-1,ans+=f[s-1](s表示这一位的位数,个位是1,十位是2……)
不要忘了加上这一位上1贡献的数,也就是10 ^ (k-1)
标程如下
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll f[10];
ll n,ans;
void solve(ll t,int s)
{
if(t==1){//处理个位 :1234?中?出现1的可能性
for(ll i=0;i<=n%10;i++){
if(i==1) ans++;
}
return;//证明处理完了
}
ll now=(n/t)%10;//当前正在处理位置上的数字
if(now==1) ans+=n%t+1;//正在处理位置上的数是1:
//从10000到12345万位上一共可以贡献2346个1
for(ll i=0;i<now;i++){//这一位上可以固定的数 (如果是1就无效了)
if(i==1)ans+=t;//这一位可以贡献的1的个数
ans+=f[s-1];//s表示这一位是从右往左第几位
}
solve(t/10,s-1);//处理下一位,相应地,s应该右移
}
int main()
{
ll a,b=1,c=1;
for(a=1;a<=9;a++){
f[a]=a;
for(ll i=1;i<a;i++)f[a]*=10;
}
scanf("%lld",&n);
while(b<n)b*=10,c++;
solve(b,c);
printf ("%lld
",ans);
return 0;
}