完全数和质数算法详解
完全数是指一个正整数,它等于其所有真约数(即除了自身以外的所有正因数)之和。例如,6 是一个完全数,因为它的真约数是 1、2 和 3,且 1 + 2 + 3 = 6。
1 计算约数和
1.1 遍历
遍历其所有可能的约数并计算它们的和。如果和等于该数,则为完全数,否则不是。
#include <iostream>
using namespace std;
int main() {
int N;
cin >> N;
while (N--) {
int X;
cin >> X;
int sum = 0;
for (int i = 1; i * i <= X; ++i) {
if (X % i == 0) {
if (i != X) sum += i;
int j = X / i;
if (j != X && j != i) sum += j;
}
}
cout << X << (sum == X ? " is" : " is not") << endl;
}
return 0;
}
1.2 生成表
#include <iostream>
#include <vector>
using namespace std;
int main() {
int N;
cin >> N;
vector<int> perfect = {6, 28, 496, 8128, 33550336};
while (N--) {
int X;
cin >> X;
bool isPerfect = false;
for (int num : perfect) {
if (num == X) {
isPerfect = true;
break;
}
}
cout << X << (isPerfect ? " is" : " is not") << endl;
}
return 0;
}
2 判断质数
2.1 错误解法 遍历
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
for (int j = 1; j <= n; j++) {
int x;
bool is_prime = true;
cin >> x;
if (x == 1)
cout << x << " is not" << endl;
else {
int i;
for (i = 2; i < x; i++) {
if (x % i == 0) {
is_prime = false;
break;
} else
is_prime = true;
}
if (is_prime)
cout << x << " is" << endl;
else
cout << x << " is not" << endl;
}
}
return 0;
}
时间复杂度:对于每个输入的数字 x x x,内层循环从 2 2 2 到 x − 1 x-1 x−1 遍历,因此单次判定的时间复杂度为 O ( x ) O(x) O(x)。如果需要判断 N N N 个数字,则总时间复杂度为 O ( N ⋅ X ) O(N \cdot X) O(N⋅X),其中 X X X 是输入数字的最大值。当 X X X 较大(例如 1 0 9 10^9 109)时,通常会TLE
2.2 正确解法
根据数学原理,一个数 x x x 如果不是质数,那么它一定可以分解为两个因数 a a a 和 b b b,且至少有一个因数小于等于 x \sqrt{x} x。因此,我们只需要检查从 2 2 2 到 x \sqrt{x} x 的所有整数即可。
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
while (n--) {
int x;
bool is_prime = true;
cin >> x;
if (x == 1)
cout << x << " is not " << endl;
else {
int i;
for (i = 2; i <= x / i; i++) {
if (x % i == 0) {
is_prime = false;
break;
}
}
if (is_prime)
cout << x << " is " << endl;
else
cout << x << " is not " << endl;
}
}
return 0;
}
时间复杂度: 对于每个数字 x x x,只需检查从 2 2 2 到 x \sqrt{x} x 的所有整数,因此单次判定的时间复杂度为 O ( x ) O(\sqrt{x}) O(x)。如果需要判断 N N N 个数字,则总时间复杂度为 O ( N ⋅ X ) O(N \cdot \sqrt{X}) O(N⋅X),其中 X X X 是输入数字的最大值。
为什么只需要检查到 x \sqrt{x} x
如果一个数 x x x不是质数,那么它一定可以分解为两个因数 a a a和 b b b,且至少有一个因数小于或等于 x \sqrt{x} x。
一个数 x x x是合数(非质数),意味着它可以被分解为两个整数的乘积: x = a ⋅ b x=a\cdot b x=a⋅b,其中 a a a和 b b b都是大于1的整数。如果 x x x是质数,则它无法被任何小于 x x x的正整数整除(除了1和自身)。
假设
x
x
x是一个合数,那么它可以写成两个因数的乘积:
x
=
a
⋅
b
x=a\cdot b
x=a⋅b
其中
a
a
a和
b
b
b都是正整数,且
a
≤
b
a\leq b
a≤b(为了简化讨论,我们可以假设
a
a
a是较小的那个因数)。
a
⋅
b
=
x
⟹
a
≤
x
或
b
≤
x
a\cdot b=x\implies a\leq\sqrt{x}\quad\text{或}\quad b\leq\sqrt{x}
a⋅b=x⟹a≤x或b≤x
因为如果 a > x a>\sqrt{x} a>x且 b > x b>\sqrt{x} b>x,那么 a ⋅ b > x ⋅ x = x a\cdot b>\sqrt{x}\cdot\sqrt{x}=x a⋅b>x⋅x=x,这与 a ⋅ b = x a\cdot b=x a⋅b=x矛盾。因此,至少有一个因数 a a a或 b b b满足 a ≤ x a\leq\sqrt{x} a≤x或 b ≤ x b\leq\sqrt{x} b≤x。换句话说,如果 x x x是合数,那么它一定存在一个小于或等于 x \sqrt{x} x的因数。
反证法:
如果 x x x是合数,则它一定存在一个小于或等于 x \sqrt{x} x的因数。
证明:
- 假设 x x x是合数,且 x x x的所有因数都大于 x \sqrt{x} x。
- 设 x = a ⋅ b x=a\cdot b x=a⋅b,其中 a a a和 b b b都是大于 x \sqrt{x} x的整数。
- 根据假设, a > x a>\sqrt{x} a>x且 b > x b>\sqrt{x} b>x。
- 因此, a ⋅ b > x ⋅ x = x a\cdot b>\sqrt{x}\cdot\sqrt{x}=x a⋅b>x⋅x=x,这与 x = a ⋅ b x=a\cdot b x=a⋅b矛盾。
- 故假设不成立,即 x x x至少存在一个小于或等于 x \sqrt{x} x的因数。
示例分析:
正例: x = 36 x=36 x=36
36 = 6 ⋅ 6 36=6\cdot6 36=6⋅6,其中 6 = 36 6=\sqrt{36} 6=36。所有因数对为 ( 1 , 36 ) , ( 2 , 18 ) , ( 3 , 12 ) , ( 4 , 9 ) , ( 6 , 6 ) (1,36),(2,18),(3,12),(4,9),(6,6) (1,36),(2,18),(3,12),(4,9),(6,6)。只需要检查 2 , 3 , 4 , 5 , 6 2,3,4,5,6 2,3,4,5,6即可找到因数。
反例: x = 73 x=73 x=73
73 73 73是质数,无法分解为两个整数的乘积。检查 2 , 3 , 4 , 5 , 6 , 7 , 8 2,3,4,5,6,7,8 2,3,4,5,6,7,8后发现 73 73 73无法被整除。