ACM博弈题型

心得体会:这些天都在做博弈的题型,略有一些心得。博弈的题水题的比较多。

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;
}






  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值