1. 程式人生 > >Uva 1378 A Funny Stone Game (ACM ICPC 2006 Asia Regional Contest Beijing A Funny Stone Game)Nim 博弈

Uva 1378 A Funny Stone Game (ACM ICPC 2006 Asia Regional Contest Beijing A Funny Stone Game)Nim 博弈

傳送門

王曉珂的題解乍一看非常難理解,且聽我慢慢解釋。

首先考慮這樣一個子問題:
當前棋子堆的編號為 m , 要求你把這個堆,並且放入兩堆編號分別小於 m 的棋子, 這裡分別計為 i, j (i, j < m)
那麼這種情況的 SG 是怎麼求的呢?
設當前編號 m 對應的局面為 Sm, 那麼 Sm 的後繼局面由不同狀態下的 (Si , Sj )
表示, 那麼我們列舉所有的 i, j ,並根據局面分解理論將他們異或起來就可以得到當前局面走一步之後的狀態局面了。
首先規定編號最小為0,因此顯然 SG[0] = 0
其餘狀態就可以列舉的找出來

void init() {
	sg[
0] = 0; memset(used, -1, sizeof(used)); for (int i = 1; i < 23; ++i) { for (int j = 0; j < i; ++j) { for (int k = 0; k < i; ++k) { used[sg[j]^sg[k]] = i; } } for (int t = 0; ; ++t) { if (used[t] != i) { sg[i] = t; break; } } } }

那麼再來看本題

可以把每一堆的棋子全部拆開來。
對於第 p 堆來說,如果他有 x 個棋子就拆成 x 堆。
為了套用我們上面討論的子問題,我們將這新的 x 堆每一堆的編號都記為 n-p-1
這樣的話從左到右拆完之後,就能保證我們玩上述子游戲的時候,必須往右邊放置。這樣對於每一堆棋子 p 的情況字遊戲就解決了。那麼根據局面分解原理,我們把 n 堆都異或起來就可以了。
因此這個題目到這裡就可以判斷勝負的。

但是更進一步的我們來簡化一下。
若果第 p 堆棋子的數量 x 為偶數,那麼偶數的數字異或的結果肯定是0,所有是沒有必要進行的。實際上我們發現,只有奇數堆才有效且只有其中的一個有效,因此對於每一堆來說,當且僅當他有奇數個棋子的時候才異或它的其中一個棋子堆(編號為 n-p-1)

題目還要求勝利狀況的下一步,根據 SG 的定義,我們只需要把下一步變為必敗狀態, 即 SG = 0 即可。那麼在我們操作第 i 堆, 變成兩堆 j, k 的時候,如果 SG 變為0就可以了。

這裡的 res^sg[n-i-1]^sg[n-j-1]^sg[n-k-1]) == 0 指的就是在當前的 res 情況下,選擇改變第 i 堆並且把它變成編號為 j 和 k 的堆, 這種情況是必敗狀態即可。

#include <bits/stdc++.h>
using namespace std;
const int maxm = 24;
const int maxn = 2e2+5;
int sg[maxm];
int used[maxn];
int a[maxm];
void init() {
	sg[0] = 0;
	memset(used, -1, sizeof(used));
	for (int i = 1; i < 23; ++i) {
		for (int j = 0; j < i; ++j) {
			for (int k = 0; k < i; ++k) {
				used[sg[j]^sg[k]] = i;
			}
		}
		for (int t = 0; ; ++t) {
			if (used[t] != i) {
				sg[i] = t;
				break;
			}
		}
	}
}
int main() {
	std::ios::sync_with_stdio(false);
	init();
	int n;
	int game = 0;
	while (cin >> n && n) {
		int res = 0;
		for (int i = 0; i < n; ++i) {
			cin >> a[i];
			if (a[i]&1) {
				res ^= sg[n-i-1];
			}
		}
		if (res == 0) {
			cout << "Game " << ++game << ": " << -1 << " " << -1 << " " << -1 << endl;
		} else {
			try {
				for (int i = 0; ; ++i) {
					if (a[i] == 0)
						continue;
					for (int j = i+1; j < n; ++j) {
						for (int k = j; k < n; ++k) {
							if ((res^sg[n-i-1]^sg[n-j-1]^sg[n-k-1]) == 0) {
								cout << "Game " << ++game << ": " << i << " " << j << " " << k << endl;
								throw std::exception();
							}
						}
					}
				}				
			} catch(...) {
				continue;
			}
		}		
	}
	return 0;
}

轉化成最初提到的新遊戲是本題的難點所在。

此外求下一步的時候,不要和求某一步 SG 混淆:

求某一步的 SG 是求出它的所有後繼局面 Sn 的子局面,並且根據局面分解原理將這些子局面進行異或操作得到後繼局面 Sn 的狀態,對所有的 Sn 求 mex 得到當前局面的 SG 值

求必勝局面的下一步是列舉自局面的可能性,選擇其中一種後繼局面 Sn 的 SG = 0(即後繼局面必敗), 按照這個選擇走