一般来说,如果单纯的一个博弈游戏的话,我们还是比较好解决的,无论是去找规律,还是递推,或者使用Nim和等。但是要是题目给出的游戏比较复杂,它甚至可以看成好几个独立的游戏,我们称这样的游戏是组合游戏的和,那么这样的游戏一般来说是很难分析并解决的。所以,对于此情况,我们需要借助一个工具,那就是大名鼎鼎的SG函数和SG定理,据说可以解决大部分的博弈问题,惊!:
1、SG函数
内容:对于任意的状态X,定义SG(X)=mex(S),其中S是X的后继状态SG函数值集合,mex(S)表示不再S内的最小非负整数,比如S={0,1,3},那么mex(S)=2。(参考训练指南136页)。
关键:对于状态X,SG(X)=0当且仅当X为必败状态。
2、SG定理
内容(关键):组合游戏的和的SG函数等于各个子游戏SG函数的Nim和,于是我们就可以对各个子游戏单独讨论了。(参考训练指南136页)。
上面给出的东西仅仅是结论,他们的思想可以抽象成图去讨论,并且涉及到了递推求解,这里就不多做介绍的,具体参看百度百科,时空转移(点击打开链接)。
理论就说到这里,到了练兵的时候了,先上一道题,HDUOJ:1536,时空转移(点击打开链接),题目如下:
S-Nim
Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 4635 Accepted Submission(s): 2011
The starting position has a number of heaps, all containing some, not necessarily equal, number of beads.
The players take turns chosing a heap and removing a positive number of beads from it.
The first player not able to make a move, loses.
Arthur and Caroll really enjoyed playing this simple game until they recently learned an easy way to always be able to find the best move:
Xor the number of beads in the heaps in the current position (i.e. if we have 2, 4 and 7 the xor-sum will be 1 as 2 xor 4 xor 7 = 1).
If the xor-sum is 0, too bad, you will lose.
Otherwise, move such that the xor-sum becomes 0. This is always possible.
It is quite easy to convince oneself that this works. Consider these facts:
The player that takes the last bead wins.
After the winning player's last move the xor-sum will be 0.
The xor-sum will change after every move.
Which means that if you make sure that the xor-sum always is 0 when you have made your move, your opponent will never be able to win, and, thus, you will win.
Understandibly it is no fun to play a game when both players know how to play perfectly (ignorance is bliss). Fourtunately, Arthur and Caroll soon came up with a similar game, S-Nim, that seemed to solve this problem. Each player is now only allowed to remove a number of beads in some predefined set S, e.g. if we have S =(2, 5) each player is only allowed to remove 2 or 5 beads. Now it is not always possible to make the xor-sum 0 and, thus, the strategy above is useless. Or is it?
your job is to write a program that determines if a position of S-Nim is a losing or a winning position. A position is a winning position if there is at least one move to a losing position. A position is a losing position if there are no moves to a losing position. This means, as expected, that a position with no legal moves is a losing position.
2 2 5 3 2 5 12 3 2 4 7 4 2 3 7 12 5 1 2 3 4 5 3 2 5 12 3 2 4 7 4 2 3 7 12 0
LWW WWL
题目有点长,但是前面几段都是背景,从倒数第二段开始看即可。大概说的是,他会给你一个集合s,然后每次拿走珠子的数量必须是那个集合中的某一个数。最后,题目会问m次,每次告诉你有多少堆和每一堆的个数,然后回答这个状态是否能赢。
分析:
题目中也说了,在这种规则下,当必输时Nim和不总是为0,所以只能另寻方法。读完题目后我们发现,题目每一次询问都会给出具体的堆数和每一堆的数量,那不其实就是单堆游戏的和吗?于是就想到SG定理,对于n堆的游戏,如果我们算出每一堆的SG,那么把它们Nim和一下,就可以得出答案了。
在给出本题源代码之前,我们有必要问一个问题,SG值具体怎么计算呢?方法是递推,下面我们就给出具体的做法,首先先给出需要使用的数组及其含义:
1、f[]:可以取走的石子个数。比如本题的集合s,但是f[]需要从小到大排序。
2、sg[]:0~n的SG函数值,这个就是我们要求的。
3、mex[]:mex{},SG函数必备元素。
针对不同情况下的SG值求法,下面再给出几条规则:
1、可选步数为1~m的连续整数,直接取模即可,SG(x) = x % (m+1)。
2.、可选步数为任意步,SG(x) = x。
3、可选步数为一系列不连续的数,用GetSG()计算。
好了,万事具备,只欠代码了,最后给出两份SG模版,一份打表,一份深搜,具体选用哪份就看情况了:
1、打表模版
const int MAXN = 1005;
int f[MAXN], sg[MAXN], mex[MAXN];
// n为SG值上限
void GetSG(int n)
{
memset(sg, 0, sizeof(sg));
for(int i=1; i<=n; ++i)
{
memset(mex, 0, sizeof(mex));
for(int j=1; f[j]<=i; ++j)
mex[sg[i-f[j]]] = 1;
for(int j=0; j<=n; ++j) if(mex[j]==0) // 求mes{}中未出现的最小的非负整数
{
sg[i] = j;
break;
}
}
}
2、dfs模版
const int MAXN1 = 105;
const int MAXN2 = 10005;
int f[MAXN1], sg[MAXN2], n;
// sg[]要初始化为-1,对于每个集合只需初始化1遍
// n是集合f的大小
int GetSG(int x)
{
if(sg[x] != -1)
return sg[x];
int vis[MAXN1];
memset(vis, 0, sizeof(vis));
for(int i=0; i<n; ++i)
if(x >= f[i])
vis[GetSG(x-f[i])] = 1;
for(int i=0; ; ++i) if(!vis[i])
{
sg[x] = i;
break;
}
return sg[x];
}
好了,有了上面的准备,我们就可以写出本题的源代码了:
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define LL __int64
using namespace std;
const int MAXN1 = 105;
const int MAXN2 = 10005;
int f[MAXN1], sg[MAXN2], n;
// sg[]要初始化为-1,对于每个集合只需初始化1遍
// n是集合f的大小
int GetSG(int x)
{
if(sg[x] != -1)
return sg[x];
int vis[MAXN1];
memset(vis, 0, sizeof(vis));
for(int i=0; i<n; ++i)
if(x >= f[i])
vis[GetSG(x-f[i])] = 1;
for(int i=0; ; ++i) if(!vis[i])
{
sg[x] = i;
break;
}
return sg[x];
}
int main()
{//freopen("sample.txt", "r", stdin);
while(~scanf("%d", &n) && n)
{
memset(sg, -1, sizeof(sg));
for(int i=0; i<n; ++i)
scanf("%d", &f[i]);
sort(f, f+n);
int m, tmp, cyc;
scanf("%d", &m);
while(m--)
{
int ans=0;
scanf("%d", &tmp);
while(tmp--)
{
scanf("%d", &cyc);
ans ^= GetSG(cyc);
}
if(ans) printf("W");
else printf("L");
}
printf("\n");
}
return 0;
}
照着模版敲下来,还是比较轻松的,其他类似的题目还有,HDUOJ:1848(可以试一下打表模版)。
哎,估计博弈论到这里可能就要放一放了,但是有关博弈的知识点远不止这些,后面遇到了再补充吧........
633

被折叠的 条评论
为什么被折叠?



