C++ NOJ 题目 “1” 的传奇

本代码在Code:Blocks 13.12环境下编译通过

noj第三季(枚举)的一道题,第一次写的时候没注意,超时了,就说怎么这么好写(doge)

后来开始找数学规律优化算法,写完后发现运行速度比参考代码还快,于是决定分享一下思路

先贴题

本题难就难受在数比较大时如何找到规律减少不必要的计算

怎么办呢?

注意到1-99(10^{2}-1)中有20个“1”,1-999(10^{3}-1)中有300个“1”,1-9999(10^{4}-1)中有4000个“1”......以此类推,可以猜想出:由1到10^{x}-1中共有x*10^{x-1}个“1”(笔者未证,数归应该可证)

现在我们知道这样的一个规律,也就意味着,对于一个N来说,我们可以对他进行分解,把他从前到后,位数一位位抽出来计算,下面举例来具体说明:

       假设N=6792,那么对于1到999时,由规律知,共有300个1,而同样的,从1000到6000的过程中,如果我们不关注首位,那么便发现在每个x000到(x+1)000的区间里,都有300个数,这样的区间由多少呢?6-1=5个,算上1-999,也就是6个区间,总共6*300个数。但一定不要忘记1000-1999这个特殊的区间,因为在这个区间中,1被重复了1000次。

       现在我们便可得出前6000个数中的1的个数,即6*300+1000。同样的道理,我们在算6001到6700时,可以假装首位6不存在,问题也就转化成了求1到700中1的个数。 可以见得,关键就在于求1到某个y*10^{x}中1的个数,不妨设其为z,而我们拥有上面的规律,便可得知,对于任意的x和y(1到9的自然数)1到y*10^{x}中1的个数为z=y*x*10^{x-1}+10^{x}

 到这里核心算法已经呼之欲出,我们只需对N进行分离,N=abcdefg,则我们只需先计算1到a000000中1的个数,再计算1到b00000中1的个数,再计算1到c0000中1的个数,以此类推,直到只剩下fg时,我们采取一个简单的for循环,计算fg中的1的个数。但就这么结束了吗?不对,尽管对于单一计算z时,y取了任意(1到9),但计算具体的N时,y=1的情况应该被讨论,因为y=1时,10^{x} 这部分是不存在的 ,而去除1后的部分在计算时,又不得不每一遍都计算上1,也就是我们不能假装1不存在。

接着思考,既然10^{x}不能用,那我们只需计算出去掉首位后剩下的数的个数再加1即可,因为这部分就相当于代替了10^{x},而因为从0开始,所以是1+(N去除掉1后,剩下的数字)。

最后我们注意一下pow函数返回类型是double以及其他小问题,代码就写好了。

#include <iostream>
#include <cmath>
using namespace std;

int main()
{
    int N,n=1,s=0,i,x,t,p;
    double y;
    cin>>N;
    for(x=1,t=N;1;x++){
        t=t/10;
        if(t==0) break;
    }
    for(;N>100;){
            y=pow(10,x-1);
            p=N/(int)y;
            if(p==1) s+=(x-1)*(y/10)+1+N-y*p;//此处运用了优化,
            if(p>=2) s+=p*(x-1)*(y/10)+y;    //即1-99有20个1,1-999有300个1
             x--;                            //以此类推,可得出递推规律,
             N-=y*p;                         //最终只需计算两位数的1的个数
    }
    for(;n<=N;n++){
        for(i=n;i>=1;){//i>=1保证了没有除超过N的位数
        if(i%10==1) s++;
        i/=10;
        }
    }
    cout<<s<<endl;
    return 0;
}

最后想说的:

这是笔者第一次在网上写这种文章,并且笔者只是一名普通的大一工科学生(非计算机),语文水平不咋地,部分表述和文字如有疏漏之处,难解之处,还望各位多多包涵并指出。

谢谢点击并阅读到这里!

  • 33
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值