心得体会:这些天都在做博弈的题型,略有一些心得。博弈的题水题的比较多。
1.博弈题一般都是找规律的题目,有的规律是已经知晓的,比如下面的1-4博弈,都是这种的,只要记住公式,和了解证明过程,熟悉公式,到具体题型时套用就可以了。
2.另外一种就是通过动态规划,找到所有的状态值,即SG值,打表,找规律。然后用类似Nim的方式,异或。
3.一般都是对称状态,处于必胜态,总可以找到一个适当的方式,让下一步处于必败态。
4.必败态是没有选择的,不论用什么方法,对手都将在下一步处于必胜态。
5.必胜态是指采用最优方案,一定能够取得胜利。必败态是指,即使采用最优方案,也一定失败。
6.决定游戏胜负的不是选择什么方案,而是由当前游戏的局势决定的。
1.巴什博弈
2.斐波那契博弈
3.威左夫博弈
4.Nim博弈
5.公平组合博弈
6.阶梯博弈
一、巴什博弈
1.问题模型:只有一堆n个物品,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个,最后取光者得胜。
2.先手必胜公式:n%(m+1)!=0
3.变形:条件不变,改为最后取光的人输。
结论:当(n-1)%(m+1)==0时后手胜利。
4.题目练习:HDOJ:2188 2149 1846
5.代码:
#include<iostream>
using namespace std;
int main()
{
int C;
int m, n;
cin >> C;
while (C--)
{
cin >> n>> m;
if (n%(m+1) == 0)cout << "Rabbit" << endl;
else cout << "Grass" << endl;
}
return 0;
}
二、威佐夫博奕
1.问题模型:有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。
2.先手必败公式: ak =[k(1+√5)/2],bk= ak + k (k=0,1,2,…,n 方括号表示取整函数)
3.题目练习:HDOJ1527&POJ1067取石子游戏
HDOJ2177取(2堆)石子游戏
4.代码:
#include<iostream>
#include<cmath>
using namespace std;
int main()
{
int M, N;
bool flag;
while (cin >> M >> N)
{
flag = false;
if (M > N)swap(M, N);
int k = 0;
k=2 * M / (1 + sqrt(5.0));
if (N-M== k) {
flag = true;
}
if (flag)cout << "0" << endl;
else cout << "1" << endl;
}
return 0;
}
三、Fibonacci博弈
1.问题模型:
有一堆个数为n的石子,游戏双方轮流取石子,满足:
(1)先手不能在第一次把所有的石子取完;
(2)之后每次可以取的石子数介于1到对手刚取的石子数的2倍之间(包含1和对手刚取的石子数的2倍)。 约定取走最后一个石子的人为赢家。
2.必败条件: 当n为Fibonacci数时,先手必败。即存在先手的必败态当且仅当石头个数为Fibonacci数。
四、尼姆博弈
1.问题模型:有三堆各若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少取一个,多者不限,最后取光者得胜。
2.必败态的规律:(a,b,c)是必败态等价于a^b^c=0(^表示异或运算)。3.poj 1704 HDOJ 1907
4.代码:
//poj 1704 Nim
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
int main()
{
int T;
int n;
int P[1010];
cin >> T;
while (T--)
{
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> P[i];
}
int x = 0;
if (n % 2 == 1) P[n++] = { 0 };//如果是奇数个,加一个数,形成一对
sort(P, P + n);
for (int i = 0; i + 1 < n; i = i + 2)
{
x ^= (P[i + 1] - P[i]-1);
}
if (x == 0)puts("Bob will win");
else puts("Georgia will win");
}
return 0;
}
//HDOJ 1907 Nim变形取走最后一个石子为输
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
int main()
{
int T;
int n;
int a[50];
cin >> T;
while (T--)
{
cin >> n;
int x = 0;
int flag = 0;
for (int i = 0; i < n; i++)
{
cin >> a[i];
if (a[i] > 1)flag++;
x ^= a[i];
}
if(n==1&&flag||flag==0&&x==0||flag!=0&&x)puts("John");
else puts("Brother");
}
return 0;
}
五、公平组合博弈(Impartial Combinatori Games)
1.定义:
(1)两人参与。
(2)游戏局面的状态集合是有限。
(3)对于同一个局面,两个游戏者的可操作集合完全相同
(4)游戏者轮流进行游戏。
(5)当无法进行操作时游戏结束,此时不能进行操作的一方算输。
(6)无论游戏如何进行,总可以在有限步数之内结束。
2.模型:给定一个有向无环图和一个起始顶点上的一枚棋子,两名选手交替的将这枚棋子沿有向边进行移动,无法移动者判负。事实上,这个游戏可以认为是所有公平组合游戏(Impartial Combinatori Games)的抽象模型。其实,任何一个ICG都可以通过把每个局势看成一个顶点,对每个局势和它的子局势连一条有向边来抽象成这个“有向图游戏”。
3.解决思路:
现在,假定我们给出两个游戏G1 和 G2。如果我们只知道单个游戏的P-状态和N-状态我们能够正确地玩好游戏和G1 + G2吗?答案是否定的。不难看出两个P-状态的和总是P-状态,P-状态和N-状态的和总是N-状态。但是两个N-状态的和既可能是P-状态也可能是N-状态。因此,只知道单个游戏的P-状态和N-状态是不够的。
为了正确地玩好游戏和我们需要推广P-状态和N-状态,它就是Sprague-Grudy函数(或者简称为g函数)
4.Sprague-Grudy定理:
令N = {0, 1, 2, 3, ...} 为自然数的集合。Sprague-Grundy 函数给游戏中的每个状态分配了一个自然数。结点v的Grundy值等于没有在v的后继的Grundy值中出现的最小自然数.
形式上:给定一个有限子集 S ⊂ N,令mex S(最小排斥值)为没有出现在S中的最小自然数。定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。
对于一个给定的有向无环图,定义关于图的每个顶点的Sprague-Garundy函数g如下:g(x)=mex{ g(y) | y是x的后继 }。
5.性质:
(1)所有的终结点所对应的顶点,其SG值为0,因为它的后继集合是空集——所有终结点是必败点(P点)。
(2)对于一个g(x)=0的顶点x,它的所有后继y都满足g(y)!=0——无论如何操作,从必败点(P点)都只能进入必胜点(N点)//对手走完又只能把N留给我们。
(3)对于一个g(x)!=0的顶点,必定存在一个后继点y满足g(y)=0——从任何必胜点(N点)操作,至少有一种方法可以进入必败点(P点)//就是那种我们要走的方法。
6.应用:
(1)可选步数为1-m的连续整数,直接取模即可,SG(x) = x % (m+1);
(2)可选步数为任意步,SG(x) = x;
(3)可选步数为一系列不连续的数,用mex(计算每个节点的值)
7.练习:hdoj 1847 1536 3980
8.代码:
HDOJ3537Daizhenyang's Coin SG打表找规律
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <string>
#include <math.h>
#include <stdlib.h>
using namespace std;
#define LL unsigned long long
#define Lowbit(x) ((x)&(-x))
#define lson l, mid, rt << 1
#define rson mid + 1, r, rt << 1|1
#define MP(a, b) make_pair(a, b)
const int INF = 0x3f3f3f3f;
const int MOD = 998244353;
const int maxn = 200000 + 7;
const double eps = 1e-8;
const double PI = acos(-1.0);
typedef pair<int, int> pii;
int sg[maxn], vis[maxn];
void init()
{
sg[0] = 1;
for (int i = 1; i <= 20; i++)
{
memset(vis, 0, sizeof(vis));
vis[0] = 1;
for (int j = 0; j < i; j++)
vis[sg[j]] = 1;
for (int j = 0; j < i; j++)
for (int k = 0; k < j; k++)
vis[sg[j] ^ sg[k]] = 1;
int j = 0;
while (vis[j]) j++;
sg[i] = j;
}
for (int i = 0; i <= 20; i++)
printf("%d %d\n", i, sg[i]);
}
int main()
{
//freopen("H:\\in.txt","r",stdin);
//freopen("H:\\out.txt","w",stdout);
//init();
int n, x;
while (scanf("%d", &n) != EOF)
{
int ans = 0;
set<int> st;
for (int i = 1; i <= n; i++)
{
scanf("%d", &x);
st.insert(x);
}
for (set<int>::iterator it = st.begin(); it != st.end(); it++)
{
x = *it;
x <<= 1;
int cnt = 0;
while (x)
{
cnt += x & 1;
x >>= 1;
}
if (cnt & 1) ans ^= 2 * (*it);
else ans ^= 2 * (*it) + 1;
}
if (!ans) puts("Yes");
else puts("No");
}
return 0;
}