题目描述:给个n,求1到n的所有数的约数个数的和;

输入描述:一行一个正整数n,n <= 100000000

输出描述:输出一个整数,表示答案;

解析:

首先我们可以知道这是一道简单的数学题,考察我们对n个数约数的求解. 我们知道对于单个数n,它的约数可以进行组合.即1和n为一组, 2和n/2为一组(前提条件为n/2为整数), 3和n/3为一组(前提条件为n/2为整数)...n^1/2和n^1/2为一组(前提条件为n^1/2为整数),所以单个数约数的求解时间复杂度可以缩短为O(n^1/2). 此时,如果按照这种求解约数的方法进行求解,对1~n每个数进行遍历,则其时间复杂度为O(n^3/2). 显然,对于n <= 100000000这个数据范围肯定会超出时间限制,因此此种方法不可取. 那么要用什么方法进行求解呢? 接下来就会用到一个十分重要的解决问题的思想--逆向思维;

对于一个数 i,我们要在1~ n个数中求有哪几个数的约数包含i,即求1~ n个数中有多少个数是 i的倍数. 我们可以用sum(i)来表示1~n中 i 的倍数的个数, 即有多少个数的约数中含有i;

约数 i i 的倍数 sum(i)
i=1 1 2 3 4...n n/1
i=2 2 4 6...n/2 n/2
i=3 3 6 9...n/3 n/3
... ... ...
i=n n n/n

则1到n的所有数的约数个数的和sum=sum(1)+sum(2)+...+sum(n)

代码如下:

#include<bits/stdc++.h>
using namespace std;
#define sc(x) scanf("%d",&x)
#define pr(x) printf("%d\n",x)
#define rep(i, l, r) for (int i = (l); i <= (r); i++)
typedef long long ll;
const int mod = 1e9+7;

int main()
{
    ll n;
    while(~scanf("%lld",&n)){
        ll sum=0;
        for(int i=1;i<=n;i++){ //遍历n次,每一次遍历求出n个数中有多少个i的倍数
            sum+=n/i; 
        }
        printf("%lld\n",sum);
    }
  
}

这样子我们就把时间复杂度降低到了O(n)级, 但其实对于上面所写的代码还可以进行细微的修改,使得其效率更高;那么要如何进行修改呢?对于上面的数据进行分析,我们不难发现: 当i > n/2 时,1~n中i 的倍数就只有一个,因此我们无需对1 ~n个数进行全部遍历,只需对前n/2进行遍历即可

优化代码如下:

#include<bits/stdc++.h>
using namespace std;
#define sc(x) scanf("%d",&x)
#define pr(x) printf("%d\n",x)
#define rep(i, l, r) for (int i = (l); i <= (r); i++)
typedef long long ll;
const int mod = 1e9+7;

int main()
{
    ll n;
    while(~scanf("%lld",&n)){
        ll sum=n-(n/2);  //当i>=(n/2)时,其倍数就只有自己本身一个,因此无需遍历,直接进行计算
        for(int i=1;i<=n/2;i++){ //遍历n/2次,每一次遍历求出n个数中有多少个i的倍数
            sum+=n/i;  
        }
        printf("%lld\n",sum);
    }

}


写在最后:虽然这是一道简单的数学题,但其中体现了逆向思维的重要性. 在算法竞赛中,题目千奇百怪,题目求解的正确与否其实并不是特别重要.其中蕴含的解决问题的思维方法才是重中之重.进行算法的学习,重点不在于刷题,做题,而在于学习其中的方法精髓.只有学好了这些,我们才能在算法竞赛中脱颖而出 ,而那些优质的思维方法不仅仅有助于我们学习竞赛,在日常生活,它也能很好的帮助我们解决实际问题,拓宽思维,从不同的角度去寻找解决问题的方法

以上就是我对这个问题的全部看法,由于能力问题,其中或许有许多不足之处,还望各位大佬能提出指正