有 n 盞燈 (編號 1~n),初始全關。k 個人依序操作:
i 人:編號為 i 的倍數按開關輸入 n, k (k ≤ n ≤ 1000),輸出最終亮燈編號。
範例:n=7, k=3 → 輸出 1 5 6 7
燈 m 被按開關的次數 = m 在 [1, k] 範圍內的因數個數。
但注意:k 可能小於 n,非完全平方數也可能亮燈 (如範例中 5, 6, 7)。
#include <stdio.h>
#include <string.h>
#define MAX_N 1000
int main() {
int n, k;
scanf("%d %d", &n, &k);
int lights[MAX_N + 1] = {0}; // 0=關, 1=亮,索引 1~n
for (int person = 1; person <= k; person++) {
for (int lamp = person; lamp <= n; lamp += person) {
lights[lamp] = !lights[lamp]; // 切換狀態
}
}
int first = 1;
for (int i = 1; i <= n; i++) {
if (lights[i]) {
if (!first) printf(" ");
printf("%d", i);
first = 0;
}
}
printf("\n");
return 0;
}
int count_divisors(int m, int k) {
int cnt = 0;
for (int d = 1; d * d <= m && d <= k; d++) {
if (m % d == 0) {
cnt++; // 因數 d
int d2 = m / d;
if (d2 != d && d2 <= k) cnt++; // 配對因數
}
}
return cnt;
}
// 主迴圈
for (int i = 1; i <= n; i++) {
if (count_divisors(i, k) % 2 == 1) {
// 輸出 i
}
}
// 使用 unsigned long long 位元陣列 (64 燈/字)
#define WORDS ((MAX_N + 63) / 64)
unsigned long long lamps[WORDS] = {0};
for (int p = 1; p <= k; p++) {
for (int m = p; m <= n; m += p) {
lamps[m >> 6] ^= (1ULL << (m & 63));
}
}
// 輸出
for (int i = 1; i <= n; i++) {
if (lamps[i >> 6] & (1ULL << (i & 63))) { /* 輸出 */ }
}
| 方法 | 時間複雜度 | 空間 | 適用場景 |
|---|---|---|---|
| 雙重迴圈模擬 | O(n·k) | O(n) | n,k ≤ 10⁴ 足夠快 |
| 因數計數 | O(n√k) | O(1) | k 很大時優於模擬 |
| 位元陣列 | O(n·k/64) | O(n/64) | 極大 n,k 且記憶體受限 |
輸入: 7 3
過程:
初始: 0 0 0 0 0 0 0
第1人: 1 1 1 1 1 1 1 (全亮)
第2人: 1 0 1 0 1 0 1 (2,4,6 切換)
第3人: 1 0 0 0 1 1 1 (3,6 切換)
結果: 1 5 6 7 ✓
開燈問題是經典的因數奇偶性應用。對於 n ≤ 1000 限制,簡單模擬法完全足夠且最不易出錯。理解數學本質有助於擴展至更大規模或變體問題 (如只輸出亮燈個數、多輪操作等)。