组合博弈入门算法笔记
取子游戏:
什么是组合游戏——
- 有两个玩家;
- 游戏的操作状态是一个有限的集合(比如:限定大小的棋盘);
- 游戏双方轮流操作;
- 双方的每次操作必须符合游戏规定;
- 当一方不能将游戏继续进行的时候,游戏结束,同时,对方为获胜方;
- 无论如何操作,游戏总能在有限次操作后结束。
概念:必败点和必胜点(P点&N点)
必败点(P点):
前一个选手(Previous player)将取胜的位置成为必败点。即下一个选手必败。
必胜点(N点):
下一个选手(Next player)将取胜的位置成为必胜点。即下一个选手必胜。
取子游戏算法实现——
**步骤1:**将所有终结位置标记为必败点(P点);
**步骤2:**将所有一步操作能进入必败点(P点)的位置标记为必胜点(N点);
**步骤3:**如果从某个点开始的所有一步操作都只能进入必胜点(N点),则将该点标记为必败点(P点);
**步骤4:**如果在步骤3未能找到新的必败点(P点),则算法终止;否则,返回到步骤2.
Nim游戏简介
1、有两个玩家;
2、有三堆扑克牌(比如:分别是5,7,9张);
3、游戏双方轮流操作;
4、玩家的每次操作是选择其中某一堆牌,然后从中取走任意张;
5、最后一次取牌的一方为获胜方;
引入概念Nim-Sum
定理一:
对于nim游戏的某个位置(x1,x2,x3),当且仅当它各部分的nim-sum等于0时(即x1^ x2^x3 = 0),则当前位于必败点。
定理一也适用于更多堆的情况~
SG函数
sg(x) = min { n >= 0 : n <> sg(y) for y ∈F(x) }
也就是说,x节点的SG值是去除x的后继节点的SG值后的最小非负整数。
SG函数的使用
必败点
:当前节点x的sg(x) = 0
必胜点
:当前节点x的sg(x) > 0
定理二:
如果图游戏G由若干子图游戏Gi组成,即:
G = G1 + ··· +Gn,假设gi是Gi(i = 1, … , n)的SG函数值,那么图游戏G的SG值计算如下:
g(x1, … , xn) = g1(x1) ^ ··· ^gn(xn)
例题:
AC代码:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int k,a[100],f[10001];//k种规则,a[]保存每次取几张牌,f[]保存后继节点的sg数
int sg(int p)
{
int t;
bool g[101] = {0};//标记后继节点的sg数
for(int i = 0; i < k; i++)
{
t = p - a[i];
if(t < 0)
{
break;
//continue;
}
if(f[t] == -1) f[t] = sg(t);//之前没有保存后继节点的sg值
g[f[t]] = 1;//标记
}
for(int i = 0; ; i++)
{
if(!g[i])
{
return i;//sg取值,去除后继节点的最小的非负整数
}
}
}
int main()
{
int n,i,m,t,s;//n组数据,m堆排
while(scanf("%d",&k) == 1 && k)
{
//if(k == 0) break;
for(i = 0; i < k; i++)
{
scanf("%d",&a[i]);
}
sort(a,a+k);
memset(f,-1,sizeof(f));
f[0] = 0;//初始化终结点
scanf("%d",&n);//n组数据
while(n--)
{
scanf("%d",&m);//m堆牌
s = 0;
while(m--)
{
scanf("%d",&t);
if(f[t] == -1)
{
f[t] = sg(t);
}
s = s ^ f[t];
}
if(s == 0)
printf("L");
else
printf("W");
}
printf("\n");
}
return 0;
}