此处为无聊之语,与博弈论无关可自行跳过:
在b站一直关注的一位算法讲堂up主突然宣布不再更新,要投身于V家大业,令人不胜感慨,此篇文章大部分是从up主视频中所学,最后完结撒花。
博弈论重要的是通过操作在N,P位置间进行转化
简单的博弈论;
(一)巴什博奕(Bash Game)
只有一堆n个物品,两人轮流从堆中取物,至少取1个,至多取m个,最后取光者胜。
我们可以发现,假设对方取了k个我们可以取m+1-k个,使得在一轮取物之后必定总取了m+1个,因此只要先手取r个,满足
(n-r) % (m+1) == 0, 即可获得胜利。
下面介绍分析此类题目的通用方法:P/N分析:
P点: 即必败点,某玩家位于此点,只要对方无失误,则必败;
N点: 即必胜点,某玩家位于此点,只要自己无失误,则必胜。
三个定理:
定理:
一、所有终结点都是必败点P(上游戏中,轮到谁拿牌,还剩0张牌的时候,此人就输了,因为无牌可取);
二、所有一步能走到必败点P的就是N点;
三、通过一步操作只能到N点的就是P点;
来个简单的水题 HDU-2149
#include<cstdio>
#include<iostream>
using namespace std;
int main()
{
int m, n;
while(~scanf("%d %d", &m, &n)){
int p = m % (n+1);
int q = m / (n+1);
if(q == 0){
cout << m ;
for(int i = m+1; i <= n; i++){
cout << " " << i ;
}
}
else if(p >= 1){
cout << p;
}
else
cout << "none" ;
cout << endl;
}
return 0;
}另附:HDU-2147,这个更无聊,找下规律就ac了。
(二)斐波那契博弈
这个我们先看一道例题: HDU--2516
参看Sample Output.
我们来看这样一定理:
Zeckendorf定理(齐肯多夫定理):任何正整数可以表示为若干个不连续的Fibonacci数之和。
比如,我们要分解83,注意到83被夹在55和89之间,于是把83可以写成83=55+28;然后再想办法分解28,28被夹在21和34之间,于是28=21+7;依此类推7=5+2,故 ;
如果n=83,我们看看这个分解有什么指导意义:假如先手取2颗,那么后手无法取5颗或更多,而5是一个Fibonacci数,如果猜测正确的话,(面临这5颗的先手实际上是整个游戏的后手)那么一定是先手取走这5颗石子中的最后一颗,而这个我们可以通过第二类归纳法来绕过,同样的道理,接下去先手取走接下来的后21颗中的最后一颗,再取走后55颗中的最后一颗,那么先手赢。
反过来如果n是Fibonacci数,比如n=89:记先手一开始所取的石子数为y,若y>=34颗(也就是89的向前两项),那么一定后手赢,因为89-34=55=34+21<2*34,所以只需要考虑先手第一次取得石子数y<34的情况即可,所以现在剩下的石子数x介于55到89之间,它一定不是一个Fibonacci数,于是我们把x分解成Fibonacci数:x=55+f[i]+…+f[j],若,如果f[j]<=2y,那么对B就是面临x局面的先手,所以根据之前的分析,B只要先取f[j]个即可,以后再按之前的分析就可保证必胜。
附上2516 ac代码:
#include<cstdio>
#include<iostream>
using namespace std;
int main()
{
int n, fib[50];
fib[0] = 2; fib[1] = 3;
for(int i = 2; i < 50; i++)
fib[i] = fib[i-1] + fib[i-2];
while(~scanf("%d", &n) && n){
int flag = 1;
for(int i = 0; i < 50; i++){
if(fib[i] == n){
flag = 0;
cout << "Second win" << endl;
}
if(fib[i] > n)
break;
}
if(flag){
cout << "First win" << endl;
}
}
return 0;
}(三)威佐夫博弈(Wythoff Game)
有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。
这种情况下是颇为复杂的。我们用(ak,bk)(ak ≤ bk,k=0,1,2,…,n)表示两堆物品的数量并称其为局势,如果甲面对(0,0),那么甲已经输了,这种局势我们称为奇异局势。前几个奇异局势是:(0,0)、(1,2)、(3,5)、(4,7)、(6,10)、(8,13)、(9,15)、(11,18)、(12,20)。
可以看出,a0=b0=0,ak是未在前面出现过的最小自然数,而 bk= ak +k,奇异局势有
如下三条性质:
1.任何自然数都包含在一个且仅有一个奇异局势中。
由于ak是未在前面出现过的最小自然数,所以有ak > ak-1 ,而 bk=ak + k > ak-1 + k-1 = bk-1 > ak-1 。所以性质1。成立。
2.任意操作都可将奇异局势变为非奇异局势。
事实上,若只改变奇异局势(ak,bk)的某一个分量,那么另一个分量不可能在其他奇异局势中,所以必然是非奇异局势。如果使(ak,bk)的两个分 量同时减少,则由于其差不变,且不可能是其他奇异局势的差,因此也是非奇异局势。
3.采用适当的方法,可以将非奇异局势变为奇异局势。
从如上性质可知,两个人如果都采用正确操作,那么面对非奇异局势,先拿者必胜;反之,则后拿者取胜。
(Betty 定理):如果存在正无理数 A, B 满足 1/A +1/B = 1,那么集合 P = { [At], t ∈ Z+}、Q = { [Bt], t ∈ Z+} 恰为集合 Z+ 的一个划分,即:P ∪ Q =Z+,P ∩ Q = ø。
上述矩阵中每一行第一列的数为 [Φi],第二列的数为 [(Φ+ 1)i],其中 Φ = (sqrt(5) + 1) / 2 为黄金分割比。
那么任给一个局势(a,b),怎样判断它是不是奇异局势呢?我们有如下公式:
ak =[k(1+√5)/2],bk= ak + k (k=0,1,2,…,n 方括号表示取整函数)
奇妙的是其中出现了黄金分割数(1+√5)/2 = 1.618…,因此,由ak,bk组成的矩形近似为黄金矩形,由于2/(1+√5)=(√5-1)/2,可以先求出j=[a(√5-1)/2],若a=[(1+√5)/2],那么a= aj,bj = aj + j,若不等于,那么a = aj+1,bj+1 = aj+1+ j +1,若都不是,那么就不是奇异局势。然后再按照上述法则进行,一定会遇到奇异局势。
例题:POJ--1067
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<iostream>
using namespace std;
int main()
{
int a, b, c;
while(~scanf("%d %d", &a, &b)){
if(a > b)
swap(a, b);
c = floor(b-a) *((sqrt(5.0)+1)/2);
if(a == c)
cout << "0" << endl;
else
cout << "1" << endl;
}
return 0;
}(四)尼姆博弈(Nim)有n堆各若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少取一个,多者不限,最后取光者得胜。(若对于为奇数堆的若干个物品,加上全体可取
k个也是Nim)
我们将k堆石子,各包含x1, x2, .... xk枚石子的状态记作位置(x1, x2, ... xk).
Nim和:
两个正整数的Nim和就是这两个数按位异或后的结果a⊕b,记作 。具有如下性质:
交换律 a⊕b = b⊕a 结合律 (a⊕b)⊕ c = a ⊕ ( b⊕c ) 自反律 a⊕a = 0 , a⊕0 = a
Bouton's定理:
Nim游戏中,一个位置(x1, x2, ... xk)是P位置,当且仅当它们的Nim和为0。
证明:
(1)结束位置为p位置,且Nim和为0。
(2)Nim和不为0时,一定存在一种方法,减小某一个变量的值,使得Nim和变为0.
(3)Nim和为0时,减小某一个变量的值,Nim和一定不为0。
我们可以将每一个数字写成二进制形式,如果每一个二进制位上1的个数都相等(称为平衡态),无论采取怎样的拿法,只会把其变为
一个存在某些二进制位上1的个数不相等的状态(称为非平衡态)
例题:HDU--1850
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
int main()
{
int n, a[105], ans, cot;
while(~scanf("%d", &n) && n){
ans = cot = 0;
memset(a, 0, sizeof(a));
for(int i = 0; i < n; i++){
cin >> a[i];
ans ^= a[i];
}
if(ans == 0)
cout << "0" << endl;
else{
for(int i = 0; i < n; i++){
int k = ans ^ a[i];
if(k < a[i])
cot++;
}
cout << cot << endl;
}
}
return 0;
}SG函数和SG定理:定义: SG(x) = mex { SG(y) | x -> y }
x -> y 表示可以从x 转移到 y,mex(Y)表示的是不存在与Y集合中的最小自然数。例如mex(0,2) = 1, mex(1,3) = 0.
在Nim游戏中可以利用SG函数进行分析, SG(x1, x2, ..., xn) = x1⊕x2⊕....xn
组合游戏的和:
假设有k个组合游戏G1,G2.....,Gk,可以定义一个新游戏,在每个回合中,双方轮流行动,任意选择一个子游戏Gi进行一次合法的操作,而保持其他游戏的
局面不变,不能操作者输,把这个新游戏称为G1,G2,...Gk的和。
Sprague-Grundy定理:
每一个简单SG-组合游戏都可以完全等效成一堆数为K的石子,其中K为该简单游戏的SG函数值。这个等效是充分必要的。
因此,即使遇到多个组合游戏同时进行,但每次只能进行一步操作的情况下,可以将其中的任何一个单一SG-组合游戏换成数目为它的SG值的一堆石子,
该单一SG-组合游戏的规则变成取石子游戏的规则(可以任意取,甚至取完),则游戏的和的胜负情况不变。这样,所有的游戏的和都等价于一个Nim游戏。
例题:HDU-1847
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int n, arr[15], sg[1005];
int mex(int x)
{
if(sg[x] != -1)
return sg[x];
bool vis[1005];
for(int i = 0; i < 1005; i++){
vis[i] = false;
}
for(int i = 0; i <= 10; i++){
int temp = x - arr[i];
if(temp < 0)
break;
sg[temp] = mex(temp);
vis[sg[temp]] = true;
}
for(int i = 0; ; i++){
if(!vis[i]){
sg[x] = i;
break;
}
}
return sg[x];
}
int main()
{
arr[0] = 1;
for(int i = 1; i <= 10; i++){
arr[i] = arr[i-1] * 2;
}
while(~scanf("%d", &n)){
memset(sg, -1 ,sizeof(sg));
if(mex(n))
cout << "Kiki" << endl;
else
cout << "Cici" << endl;
}
return 0;
}
5万+

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



