基本概念
组合游戏:
- 有2个玩家。
- 游戏的操作状态是一个有限的集合(eg. 限定大小的棋盘)。
- 游戏双方轮流操作。
- 双方每次操作必须符合游戏规定。
- 当一方不能将游戏继续进行的时候游戏结束,对方胜利。
- 无论如何操作,游戏总可以在有限次操作后结束。
必败点(P点):前一个选手(Previous Player)将取胜的位置。
必胜点(N点):下一个选手(Next Player)将取胜的位置。(两个选手都足够聪明,不会走错)
必败(必胜)点属性:
- 所有终结点(进行不下去的位置)是必败点。
- 从任何必胜点操作,至少有一种方法可以进入必败点。
- 无论如何操作,从必败点都只能进入必胜点。
eg. 取子游戏算法实现 (具体实现时可以找规律)
- 将所有终结位置标记为必败点。
- 将所有一步就可以进入必败点的位置标记为必胜点。
- 如果从某个点开始的所有一步操作都只能进入必胜点,则将该点标记为必败点。
- 如果在步骤3未能找到新的必败点,则算法终止;否则返回到步骤2。
Nim游戏
游戏简介
- 有两个玩家。
- 有三堆扑克牌。
- 游戏双方轮流操作。玩家每次操作是选择其中某一堆牌,然后从中取走任意张。
- 最后一次取牌的一方为获胜方。
游戏分析
Nim-Sum:按位异或运算
假设和
的Nim-Sum是
,则我们表示成:
。
定理一
对于Nim游戏的某个位置,当且仅当它的各部分的Nim-Sum等于0时(即
),则当前位于必败点。(不等于0就位于必胜点)
证明(依据必胜点必败点三条属性):
- 终结位置几堆牌的数量都是0,Nim-Sum肯定是0。
- 不等于0的Nim-Sum至少有一种方法可以使它变成0(改变某一项的二进制位,从1改成0,或者从0改成1,因为结果位为1的上面一定对应着奇数个1)。
- 如果结果为0,那么改变任意一项都会使结果非0。
变式
问:对于必胜点有几种可行的操作方案?
解:只需要判断有几堆牌的张数(二进制表示)能够满足Nim-Sum(二进制表示)的最高位1的。(具体只需要把这一堆牌数对应的结果中为1的位相应取反就可以实现把Nim-Sum变成0。)
图游戏与SG函数 GraphGame&Sprague-GrundyFunction
基本思想
对于一个图游戏(Nim游戏)来说,由于它的状态太明显了,完全可以使用搜索来做。
可以画出状态转移图。
SG函数:
即: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值计算如下:。
经典例题
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;
}