HJ56 完全数计算

195 阅读2分钟

Day14 2023/01/21

题目链接

难度:简单

题目

完全数(Perfect number),又称完美数或完备数,是一些特殊的自然数。

它所有的真因子(即除了自身以外的约数)的和(即因子函数),恰好等于它本身。

例如:28,它有约数1、2、4、7、14、28,除去它本身28外,其余5个数相加,1+2+4+7+14=28。

输入n,请输出n以内(含n)完全数的个数。

数据范围:1n51051 ≤ n ≤ 5*10^5 

输入描述:

输入一个数字n

输出描述:

输出不超过n的完全数的个数

示例

输入:1000
输出:3

思路一


采用暴力解法,计算1~n范围内每个数的真因子(即除了自身以外的约数)之和,如果等于本身,即该数就是完全数,接着计数器加1,最后返回计数器结果即可。

思路二


采用欧拉公式计算。
欧拉公式说明:

  • 如果pp是质数,且2p12^p-1也是质数,那么(2p1)2p1(2^p-1)*2^{p-1}便是一个完全数。

  • 例如p=2p=2,是一个质数,2p1=32^p-1=3也是质数,(2p1)2p1=32=62^p-1)*2^{p-1}=3*2=6,是完全数。

  • 例如p=3p=3,是一个质数,2p1=72^p-1=7也是质数,(2p1)2p1=74=282^p-1)*2^{p-1}=7*4=28,是完全数。

关键点


  • 思路一中外层循环控制被除数,内层循环控制除数,并判断该除数是否为真因数。

  • 虽然思路一中采用的是暴力解法,但是优化了内层循环的循环次数。当j <= n/2 时即可退出循环。 因为没有大于自身 1/2 的真因子(这是个数学问题)

  • 思路二中自定义的函数is_prime()用来判断是否为质数,其中循环退出的条件i <= sqrt(p);(即小于等于自身的平方根) ,这里不懂的同学可以看一下这个:链接

算法实现


c++代码实现1-暴力解法

#include <iostream>
#include <cmath>
using namespace std;
bool is_prime(int p);

//方法一:暴力解法
int main() {
    int n;
    cin >> n;      //输入n
    int count = 0; // 计数器
    for (int i = 2; i <= n; i++) {  //1肯定不是完全数
        int sum = 1;   //累加器,1是任何数的约数
        for(int j = 2; j <= n/2; j++) { //大于自身1/2的约数是不存在的
            if(i % j == 0 && i != j) sum += j; //i为被除数,j为除数。真因子不包括自生
        }
        if(sum == i) count++; //如果是完全数就加1
    }
    cout << "完全数个数为:" << count;
    return 0;
}
  • 时间复杂度 O(n2)O(n^2) ---双层嵌套循环,其中n为数值大小,实际循环次数要小于n2n^2
  • 空间复杂度 O(1)O(1) --- 没有额外的辅助空间

c++代码实现2-欧拉方程

#include <cmath>
#include <iostream>
using namespace std;
bool is_prime(int p);

// 方法二:欧拉公式
// 如果p是质数,且2^p-1也是质数,那么(2^p-1)X2^(p-1)便是一个完全数
int main() {
    int n;
    cin >> n;
    int count = 0; //计数器
    for (int p = 2; p <= n; p++) { //1肯定不是完全数
        int t = pow(2, p) - 1;
        if (is_prime(p) && is_prime(t)) { //满足欧拉方程的要求,计算完全数
            int perfect_num = pow(2, p - 1) * t;
            if (perfect_num <= n)
                count++;
            else
                break; //当大于n时就跳出循环,大大降低了循环次数
        }
    }
    cout << "完全数个数为:" << count;
}

//判断是否为质数
bool is_prime(int p) {
    for (int i = 2; i <= sqrt(p); i++) //判断是否有除了1和自生的因数(约数)
        if (p % i == 0)
            return false;
    return true;
}
  • 时间复杂度 O(nm)O(n*m) --- 其中n为数值大小,m为当前判断数的平方根大小,最终循环次数远小于 n*m,当然也远小于暴力解法
  • 空间复杂度 O(1)O(1) --- 没有额外的辅助空间

总结

  • 感觉本题更偏向于数学问题。