完美數指等於其真因子和的正整數 (如 6=1+2+3, 28=1+2+4+7+14)。已知完美數極稀少且增長極快,本文採用預計算已知完美數配合區間查詢,實現 O(1) 查詢效能的 C 程式解法。
#include <stdio.h>
#include <stdint.h>
int main() {
// 已知完美數 (64位元範圍內僅 8 個)
const uint64_t perfect[] = {
6ULL, 28ULL, 496ULL, 8128ULL,
33550336ULL, 8589869056ULL,
137438691328ULL, 2305843008139952128ULL
};
const int COUNT = sizeof(perfect) / sizeof(perfect[0]);
uint64_t m, n;
while (scanf("%llu %llu", &m, &n) == 2) {
if (m == 0 && n == 0) break;
if (m > n) { uint64_t t = m; m = n; n = t; } // 確保 m <= n
int found = 0;
for (int i = 0; i < COUNT; i++) {
if (perfect[i] >= m && perfect[i] <= n) {
if (found) printf(" ");
printf("%llu", perfect[i]);
found = 1;
}
}
if (!found) printf("No");
printf("\n");
}
return 0;
}
原始碼使用雙重迴圈尋找邊界索引 i、j,再輸出 [j, i) 區間。改進版直接單迴圈篩選,邏輯更清晰、避免邊界錯誤。
uint64_t 支援第 8 個完美數 (2.3×10¹⁸)%llu 對應 unsigned long longm==0 && n==0若不使用查表,需對區間每個數測試是否為完美數:
// 測試單一數是否為完美數 - O(√n)
int is_perfect(uint64_t x) {
if (x <= 1) return 0;
uint64_t sum = 1;
for (uint64_t i = 2; i * i <= x; i++) {
if (x % i == 0) {
sum += i;
if (i != x / i) sum += x / i;
}
}
return sum == x;
}
但對大區間極其低效,查表法在已知完美數極少時為最優解。
// 已知梅森質數指數 p (前 8 個)
int mersenne_p[] = {2, 3, 5, 7, 13, 17, 19, 31};
uint64_t gen_perfect(int p) {
return ((1ULL << (p-1)) * ((1ULL << p) - 1));
}
// 注意:p=31 時 1ULL<<31 溢位 32 位元,需用 128 位元或大數庫
第 9 個完美數有 37 位數字,需大數庫:
// 使用 GMP 庫
#include <gmp.h>
void gen_perfect_gmp(mpz_t result, int p) {
mpz_t mersenne;
mpz_init(mersenne);
mpz_ui_pow_ui(mersenne, 2, p); // 2^p
mpz_sub_ui(mersenne, mersenne, 1); // 2^p - 1
mpz_mul_2exp(result, mersenne, p-1); // * 2^(p-1)
mpz_clear(mersenne);
}
| 方法 | 預處理 | 單次查詢 | 適用場景 |
|---|---|---|---|
| 查表法 (本文) | O(1) | O(1) ~ 8 次比較 | 已知完美數範圍內最優 |
| 動態試除 | 無 | O(n√n) | 教學驗證、未知完美數 |
| 梅森質數生成 | O(p²) 大數運算 | O(1) 查表 | 需生成超大完美數 |
完美數問題展現「已知數學知識優於暴力計算」的經典案例。在已知解空間極小且固定時,查表法以極簡程式碼達成理論最優效能,是演算法設計中「問題特性分析」重要性的最佳佐證。