ACM组合博弈SimpleGameTheory

本文介绍了组合游戏中的必败点和必胜点概念,以及Nim游戏的Nim-Sum分析,重点讲解了SG函数在图游戏中作为状态评估的方法。通过示例和编程代码展示了如何利用SG函数解决S-Nim问题,以及如何组合多个小游戏。
摘要由CSDN通过智能技术生成

基本概念

组合游戏

  1. 有2个玩家。
  2. 游戏的操作状态是一个有限的集合(eg. 限定大小的棋盘)。
  3. 游戏双方轮流操作。
  4. 双方每次操作必须符合游戏规定。
  5. 当一方不能将游戏继续进行的时候游戏结束,对方胜利。
  6. 无论如何操作,游戏总可以在有限次操作后结束。

必败点P点):前一个选手(Previous Player)将取胜的位置。

必胜点N点):下一个选手(Next Player)将取胜的位置。(两个选手都足够聪明,不会走错)

必败(必胜)点属性

  1. 所有终结点(进行不下去的位置)是必败点。
  2. 从任何必胜点操作,至少有一种方法可以进入必败点。
  3. 无论如何操作,从必败点都只能进入必胜点。

eg. 取子游戏算法实现 (具体实现时可以找规律

  1. 将所有终结位置标记为必败点。
  2. 将所有一步就可以进入必败点的位置标记为必胜点。
  3. 如果从某个点开始的所有一步操作都只能进入必胜点,则将该点标记为必败点。
  4. 如果在步骤3未能找到新的必败点,则算法终止;否则返回到步骤2。

Nim游戏

游戏简介

  1. 有两个玩家。
  2. 有三堆扑克牌。
  3. 游戏双方轮流操作。玩家每次操作是选择其中某一堆牌,然后从中取走任意张。
  4. 最后一次取牌的一方为获胜方。

游戏分析

Nim-Sum:按位异或运算

        假设(x_{m}\cdots x_{0})_{2}(y_{m}\cdots y_{0})_{2}的Nim-Sum是(z_{m}\cdots z_{0})_{2},则我们表示成:(x_{m}\cdots x_{0})_{2}\oplus (y_{m}\cdots y_{0})_{2}=(z_{m}\cdots z_{0})_{2}

定理一

对于Nim游戏的某个位置(x_1,x_2,x_3),当且仅当它的各部分的Nim-Sum等于0时(即x_1\oplus x_2 \oplus x_3=0),则当前位于必败点。(不等于0就位于必胜点)

证明(依据必胜点必败点三条属性):

  1. 终结位置几堆牌的数量都是0,Nim-Sum肯定是0。
  2. 不等于0的Nim-Sum至少有一种方法可以使它变成0(改变某一项的二进制位,从1改成0,或者从0改成1,因为结果位为1的上面一定对应着奇数个1)。
  3. 如果结果为0,那么改变任意一项都会使结果非0。

变式

:对于必胜点有几种可行的操作方案?

:只需要判断有几堆牌的张数(二进制表示)能够满足Nim-Sum(二进制表示)的最高位1的。(具体只需要把这一堆牌数对应的结果中为1的位相应取反就可以实现把Nim-Sum变成0。)


图游戏与SG函数 GraphGame&Sprague-GrundyFunction

基本思想

对于一个图游戏(Nim游戏)来说,由于它的状态太明显了,完全可以使用搜索来做。

可以画出状态转移图

SG函数sg(x)=Min\{ n\geq 0:n\neq sg(y)\, for\, y\in F(x)\}

        即:x节点的SG值是   除去   x的后继结点的   SG值后的   最小非负整数。(要从最后一个节点开始看,最后一个节点的SG值就是0)

定理SG值为0就是必败点,不为0就是必胜点。(证明依照必胜点必败点三条属性)


组合游戏的并

将多个小游戏组合在一起。

eg. 三堆扑克牌(分别为5、7、9张),双方轮流取牌,每次可以选择任意一堆牌取走1~3张。

定理二

如果图游戏G由若干个子图游戏Gi组成,即:G = G1 + ... + Gn,假设gi是Gi的SG值,那么图游戏G的SG值计算如下:g(x_1,\cdots,x_n)=g_1(x_1)\oplus\cdots\oplus g_n(x_n)

经典例题

S-Nim

        :对于每一种规则,每一种游戏都使用SG函数即可。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

int ruleNum, rule[100], testNum, heapNum, heap, s, f[10001];    //f保存sg值,记忆化dfs
int sg(int);

int main()
{
	while(scanf("%d", &ruleNum), ruleNum)
	{
		for(int i = 0; i < ruleNum; i++)
			scanf("%d", &rule[i]);
		sort(rule, rule+ruleNum);    //这里排序便于之后求sg值时从小往大试
		memset(f, -1, sizeof(f));
		f[0] = 0;
		scanf("%d", &testNum);
		while(testNum--)
		{
			scanf("%d", &heapNum);
			s = 0;
			while(heapNum--)
			{
				scanf("%d", &heap);
				if(f[heap] == -1)
					f[heap] = sg(heap);
				s ^= f[heap];
			}
			if(s == 0) printf("L");
			else printf("W");
		}
		printf("\n");
	}
	return 0;
}

int sg(int p)    //求sg值,完全按照sg函数定义来写
{
	int t;
	bool visit[101] = {0};    //标记数组
	for(int i = 0; i < ruleNum; i++)
	{
		t = p - rule[i];
		if(t < 0) break;
		if(f[t] == -1)
			f[t] = sg(t);
		visit[f[t]] = 1;
	}
	for(int i = 0; ; i++)
		if(!visit[i])
			return i;
}
  • 14
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值