题目介绍
直观枚举
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
int main() {
int num_cases; // 测试用例数量
cin >> num_cases;
for (int t = 0; t < num_cases; ++t) {
int num; // 输入的整数
cin >> num;
int sum_divisors = 0; // 约数之和
// 计算真约数之和
for (int divisor = 1; divisor < num / 2 + 1; ++divisor) {
if (num % divisor == 0) {
sum_divisors += divisor;
}
}
if (sum_divisors == num) {
cout << num << " is perfect" << endl; // 完美数
} else {
cout << num << " is not perfect" << endl; // 非完美数
}
}
return 0;
}
题目是除本身之外的约数,故这是最直接的枚举。这里要指出的是为什么我们只考虑到原数除以2呢?**是因为一个正整数的约数是不可能大于它的一半的(除了它本身,题目这里略过了)。**因为如果他是偶数那么二是他的约数,就刚好等于一半。如果是奇数,最大不可能有一半。这里附上chatgpt的证明。(+1是为了6这种一半也是约数的情况。)
但是无情超时,原因在于1sC++至多处理1亿次,而题目范围双层循环外100次*内部给的范围是1亿。实际上超出了100倍,而这样的结论只能少判断一半,仍然超过50倍。
开根号改进
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
int main() {
int test_cases; // 测试用例数量
cin >> test_cases;
for (int t = 0; t < test_cases; ++t) {
int num; // 输入的整数
cin >> num;
int sum_divisors = 0; // 约数之和
for (int i = 1; i < sqrt(num); ++i) {
if (num % i == 0) {
sum_divisors += i;
sum_divisors += num / i;
}
}
sum_divisors -= num; // 减去自身
if (sum_divisors == num) {
cout << num << " is perfect" << endl; // 完美数
} else {
cout << num << " is not perfect" << endl; // 非完美数
}
}
return 0;
}
依旧上传chatgpt的证明
然而在这里我犯了一个错误,也是我要写这篇题记的根源。这便是我下意识写成了在i < sqrt(num)+1
,这个+1的习惯帮助了我很多次,但是这一次想当然了。
- 对于许多数来说,+1的写法也问题不大,只是多判了一次循环,也不影响时间复杂度。
- 然而对此题来说,当输入1时,不加1都不会进入循环,+1却多循环一次,从而判错;当输入6时,开根号小于3,+1变成4,由于3也恰好是约数,又重复计算了一遍。
总结
1.约数的问题一律开根号,复杂度从百亿降到万。
2.对于循环是否+1的问题,需要具体问题具体分析。