題目來源:ECJTU 2008 Summer Contest
核心標籤:博弈論、威佐夫博弈、黃金分割、數學推導
兩堆石子 (a, b),a ≤ b ≤ 1,000,000。雙方輪流操作,可選:
取走最後石子者勝。給定初始狀態,先手是否必勝?若必勝,列出所有第一步獲勝移動(優先輸出「同步取走」類型)。
定義一系列有序對 (a_k, b_k)(k = 0, 1, 2...):
a_0 = b_0 = 0a_k 為未在前面出現過的最小自然數b_k = a_k + k前幾項:(0,0), (1,2), (3,5), (4,7), (6,10)...
結論:面對奇異局勢 = 必敗(P-position);否則 = 必勝(N-position)。
令 φ = (1+√5)/2 ≈ 1.618(黃金比例),則:
a_k = ⌊k × φ⌋
b_k = a_k + k
給定 (a, b),設 k = b - a,計算 a_k = ⌊k × φ⌋:
a_k == a → 奇異局勢 → 輸出 0必勝時需找出所有能轉移到某奇異局勢 (a_k, b_k) 的移動。四種可能來源:
a - a_k == b - b_k 且 a > a_ka 為奇異局勢的 a_k):反推 k = ⌊2a/(1+√5)⌋ + 1a 為奇異局勢的 b_k):反推 k = ⌊2a/(3+√5)⌋ + 1b 為奇異局勢的 b_k):反推 k = ⌊2b/(3+√5)⌋ + 1每種情況驗證反推出的 (a_k, b_k) 是否合法且滿足取石子約束,合法則輸出。
const double phi = (1.0 + sqrt(5.0)) / 2.0;
while (scanf("%d%d", &a, &b), a || b) {
if (a > b) swap(a, b);
if (a == 0) { printf("1\n0 0\n"); continue; } // 直接取光 b 堆
int k = b - a;
int ak = (int)(k * phi);
if (ak == a) { printf("0\n"); continue; } // 奇異局勢
printf("1\n");
int bk = ak + k;
// 1. 同步取走
if (a - ak == b - bk && a > ak) printf("%d %d\n", ak, bk);
// 2. 只取 b,a 為 a_k
k = (int)(2.0 * a / (1 + phi)) + 1;
bk = a + k; ak = (int)(k * phi);
if (ak == a && b > bk) printf("%d %d\n", a, bk);
// 3. 只取 b,a 為 b_k
k = (int)(2.0 * a / (3 + phi)) + 1;
bk = a; ak = (int)(k * phi);
if (bk - k == ak) printf("%d %d\n", ak, a);
// 4. 只取 a,b 為 b_k
k = (int)(2.0 * b / (3 + phi)) + 1;
bk = b; ak = (int)(k * phi);
if (bk - k == ak && ak <= a && a != b) printf("%d %d\n", ak, b);
}
O(1) 每測資,極快double 足夠支撐 1e6 範圍,若擴大至 1e9 需用 long double 或整數模擬威佐夫博弈是博弈論中極具美感的經典模型,掌握其黃金分割特性,可瞬間解決大量變體題目。