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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/HZEUHJEJNJ/article/details/84660565

传送门

王晓珂的题解乍一看非常难理解,且听我慢慢解释。

首先考虑这样一个子问题:
当前棋子堆的编号为 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(即后继局面必败), 按照这个选择走

猜你喜欢

转载自blog.csdn.net/HZEUHJEJNJ/article/details/84660565