UVA 1730 Sum of MSLCM 【数学】【二分】

题目链接:https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=4926

 

题目大意:  定义一个数的所有因子(包括自身也包括1)的和,为这个数 的MLCM。 题目要求的是,给出一个n,1<n<20000001,求出  从2到n的所有数(以下找规律时是算进了1,结果出来后会减掉)的 MLCM的和。

多组输入数据。每组最多有100个。

 

恕我愚昧,这道题我想了好几次,都没有思路便草草放弃,今天终决心啃下来,没想到A了。

 

起初先想到是唯一分解定理。求一个数的所有因子和问题,然后O(n)的时间全部累加起来。然而发现算上数据组数 10^9必然是超时结局。

 

百般无奈之下,百度了一下众位大佬的思路,结果 有一种方法是  类似埃及筛法的暴力打表法,还有一种是用了神奇的因式分解,还有一种是二分......

 

最后,都没有看懂。

 

但是有一句话突然让我注意到了一点, (很好奇是怎么发现的)任何一个数 i ,在题意要求的的所有因子里面出现了  n/i 次 。

       (再强调。。这里整理思路的时候是加了 1的,后面结果会减掉)

 

也就是说,第一个样例里面   10,  满足题意的所有因子中 

1出现了10次

2出现了5次

3出现了3次

4出现了2次

5出现了2次

6出现了1次

7、8、9、10都只出现了1次

 

发现从1到10, 出现的次数都是顺序递减的。而接下来的操作只要  把 每个因子 乘以 它出现个数 ,全部加起来 就是题意所求了。

这样求出各个因子的值  并且 相加 ,还是O(n)啊。

 

对于每个因子从 1 , 2 , 3 , 4 4 , 5 , 6 , 7 , 8 , ....... ,n  他们的出现次数由大到小递减。

相加过程能不能用数学方法进行跳跃相加呢? 我认真的思考这个问题,没想出来......

但是跳跃相加! 在这个 各因子的出现次数 都是顺序递减的 这个序列里,不就可以用二分查找了!

从左边开始 对不同的次数的区间边界进行二分查找,使得查找得到的区间,区间内的所有数的出现的次数都是一样的。 然后对区间里的数字运用求和公式即可。

在查找前,不需要先把每个数字的出现次数预处理出来。可以边查边算。

 

敲了一下代码  发现 即使是 20000000,出现的不同的次数 ,是8942个。

而 即使 是  log(20000000)  也就是16.12

也就是说,按照题目的最坏的情况,也只需要 二分查找8942次,每次折半查找16次,不会超时。

 

下面上代码。

 

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

int main()
{
    int l,r,i,j,n,m,value,index;
    ll ans;
    while(~scanf("%d",&n))
    {
        if(!n)break;
        ans=0;                       //ans计入答案

        l=1;                         //二分查找初始化
        r=n;
        
        index=1;                     //计入每一次二分查找的起点
        value=n/index;               //value为二分查找的目标左区间(最左侧数字的次数)
        while(1)
        {
            if(index>n)break;        //左区间超出范围则停止
            
            while(l<=r)              //二分查找 最后的r是右区间的位置
            {m=(l+r)>>1;
            if(n/m<value)r=m-1;
            else l=m+1;
            }
            
            ans+=(ll)(index+r)*(r-index+1)*(n/index)/2;       //ans加上区间的各数*出现次数   求和公式一下   注意“强制转换”不可省略!!
            
            index=r+1;                      //一趟结束,为下一次的二分做 “向右迈进”
            value=n/index;
            l=index;
            r=n;
        }
        printf("%lld\n",ans-1);             //要减去一个1,上文声明过
    }
    return 0;
}

 

 

 

完毕~~(结果和唯一分解定理 没有半分关系~)

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值