POJ 2311 Cutting Game 博弈论 SG 函数的理解

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

传送门

这题是对 SG 函数本质理解的一个绝佳的题目
之前我们做题的时候大多都是 Nim 博弈,想当然的认为 SG 函数用二进制数表示一个局面用的是棋子个数,并且也只适用于这个局面,甚至认为 SG 函数只能解决 Nim 类的题目,并且想当然的认为最初状态(即0的时候) SG = 0

实际上,SG 函数的适用范围远远比此广得多。而且 SG 函数用来表示局面的二进制只要合理能够区分局面,并且能够自圆其说的转移状态,那么 SG 函数就是正确的。

本题中,用了二维的 SG 来表示当前局面,并且推的过程中充分考虑了题意来寻找最终状态

SG 函数应该在最终状态(即无法操作态)返回 0, 这题中,如果仅仅认为是 W == H == 1的时候是最终失败态就错了。 实际上,当你手中拿着一个 n1 (n >= 2)的格子的时候,剪一刀你就赢了,而对手这个时候手里是 m1 (m >=1),如果你写的循环是从 1 开始枚举后继状态并将他们异或的就错了,因为这个时候已经无法操作了,你却认为可以继续分解。换句话说,你不能将当前局面拆分成独立的子游戏了,因为你这一步已经赢了。不独立的子游戏之间不能随便用局面分解原理了。

此外 SG 函数不能简单的将赢的局面返回 1 。比如你手里是 7*1 的格子你就赢了,但是你直接返回1是不妥的。SG 函数只有必败态才是确定的 0 ,其余状态都是异或出来的一个非零值,不一定是 1,如果你将赢的局面粗暴的赋值为 1,可能会对其他的异或判断产生影响,因此不能对赢的局面简单的赋值 1

本题的结束状态既然不是 W == H == 1 ,那么是什么呢?既然不方便判断,我们可以将这个最终状态往上层再拉一步。你发现,当你手里的局面是 22, 23, 32, 33 的时候你是必输的,其余情况则不能一眼看出来需要进一步判断。那么就可以将这四个当成必败的最终状态。并且根据必败的最终状态的 SG 值为 0 的规则对这四个状态赋值为 0 。这实际上是将最终态上拉到了一个可以拆分成独立子游戏的地方。

到这里总结一下:

  1. SG 函数表示不必拘泥于二进制形式和 Nim 的关系
  2. SG 函数最底层的值是无法继续操作的最终步骤,将他们赋值为 0, 而不是简单的 0 的情况
#include <iostream>
#include <cstring>
using namespace std;
const int maxn = 2e2+5;
const int maxl = 1e3+50;
int sg[maxn][maxn];
int fun(int W, int H) {
	if (sg[W][H] > -1)
		return sg[W][H];
	bool vis[maxl];
	memset(vis, false, sizeof(vis));
	for (int i = 2; i < W-1; ++i) {
		vis[fun(i, H)^fun(W-i, H)] = true;
	}
	for (int i = 2; i < H-1; ++i) {
		vis[fun(W, i)^fun(W, H-i)] = true;
	}
	for (int i = 0; ; ++i) {
		if (!vis[i]) {
			sg[W][H] = i;
			return sg[W][H];
		}
	}
}

int main() {
	int W, H;
	memset(sg, -1, sizeof(sg));
	while (cin >> W >> H) {
		if (fun(W, H)) {
			cout << "WIN" << endl;
		} else {
			cout << "LOSE" << endl;
		}
	}
}

猜你喜欢

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