博弈论
奇异局势: 面对此局势, 不管做出任何动作, 都将输掉最终比赛.
巴什博奕(Bash Game)
问题:一堆n个物品, 两个人轮流从这堆中取物品, 规定每次至少取一个, 最多取m个, 最后取光者胜, 先取如何必胜?(n>m+1) 思路:当n%(m+1)==0时为奇异局势, 第一次通过取n%(m+1)个到达奇异局势, 接下来对手取x个, 取m+1-x个继续达到奇异局势, 重复此步骤.
- 判断能否取胜
n, m = 20, 6
if n%(m+1) == 0:
print('no')
else:
print('yes')
- 第一步先手的取法
n, m = 20, 6
if n%(m+1) == 0:
print('no')
else:
print('take ' + str(n%(m+1)))
威佐夫博奕(Wythoff Game)
问题:两堆物品分别有m, n个, 两个人轮流从某一堆或同时从两堆中取同样多的物品, 规定每次至少取一个, 多者不限, 最后取光者得胜, 先取如何必胜?(n>0, m>0) 思路:(1, 2), (3, 5), (4, 7), (6, 10)...这些局势是奇异局势, 称为(ak, bk), 非奇异局势可以通过一次操作达到奇异局势(ak<bk) k代表第k个, 也正好是b-a, 而奇异局势不能通过一次操作来得到另一个奇异局势.
局势 | 取法 |
---|---|
a==b | 2堆同取a个 |
a==ak且b>bk | b堆取b-bk个 |
a==ak且b<bk | 2堆同取a-aj(j==b-a)个 |
a>ak且b==bk | a堆取a-ak个 |
a==aj(j<k)<ak且b==bk | b堆取b-bj个 |
a==bj(j<k)<ak且b==bk | b堆取-aj个 |
奇异局势公式: (ak=[k(1+√5)/2], bk=ak+k)
- 判断能否取胜
import math
a, b = 2, 1
if a > b:
a, b = b, a
k = b - a
ak = (math.sqrt(5)+1)*k//2
# bk==ak+k
if ak == a:
print('no')
else:
print('yes')
第一步先手取法参见表格中的步骤
尼姆博奕(Nim Game)
问题:有3堆物品, 各若干个, 两个人轮流从某一堆取任意多的物品, 规定每次至少取一个, 多者不限,先取如何必胜? 思路: 任意局势(a, b, c), 当a^b^c=0时, 为奇异局势, 面对局势(a, b, c)时(a>b>c), 当a^b^c不为0时, 可以通过让c=c-a^b(c>a^b)来达到奇异局势, 这个结论可以扩充到N堆物品.
- 判断能否取胜
L = [5, 10, 12]
nim = 0
for item in L:
nim ^= item
if nim == 0:
print('no')
else:
print('yes')
- 第一步先手可以的取法
L = [5, 10, 12]
nim = 0
for item in L:
nim ^= item
if nim == 0:
print('no')
else:
for idx, item in enumerate(L):
if nim^item < item:
print('from index {0} take {1}'.format(idx, item-(nim^item)))
SG函数
sg函数是解决复杂博弈问题的通用方法, 将博弈问题的原始解法(N/P)抽象成初始状态为顶点, 后继结点为后继状态的有向无环图. 定义一种运算mex({...}), 计算集合中最小的非负整数. 可以将一个复杂博弈问题抽象成几个博弈小问题 最后的sg值为所有博弈小问题的sg值的异或 sg(x)=mex(sg(y)|y是x的后继结点)
巴什博奕加强版
问题:一堆n个物品, 两个人轮流从这堆中取物品, 规定每次至少要取的物品是2的幂, 最后取光者胜, 先取如何必胜? 思路:使用sg函数.
N = 16
sg = [0 for i in range(N+1)]
def mex(x):
global N, sg
vis = [False for i in range(N+1)]
i = 1
while x+i <= N:
vis[sg[x+i]] = True
i *= 2
i = 0
while True:
if not vis[i]:
return i
i += 1
for i in range(N-1, -1, -1):
sg[i] = mex(i)
win = 'lose'
i = 1
while i <= N:
if sg[i] == 0:
win = 'win, take %d .' % i
break
i *= 2
print(win)
通过打印sg数组发现规律(N%3==0出现奇异局势) 简化后的程序
N = 16
if N%3 == 0:
print('lose')
else:
print('win, take {0} .'.format(N%3))
尼姆博奕加强版
问题:有3堆物品, 各若干个, 两个人轮流从某一堆取F个物品, F为菲波那契数列中的任意一个数(F(1)=1), 先取如何必胜? 思路: 使用sg函数, 分离成三个子问题, 最后将sg值异或求出最终sg值.
N = 15
fib = [0 for i in range(N)]
def calc_fib():
global fib
fib[1], fib[2] = 1, 2
for i in range(3, N):
fib[i] = fib[i-1] + fib[i-2]
calc_fib()
L = [3, 4, 5]
sg = [0 for i in range(N)]
def mex(x):
global N, sg
vis = [False for i in range(N)]
i = 1
while fib[i] <= x:
vis[sg[x-fib[i]]] = True
i += 1
i = 0
while True:
if not vis[i]:
return i
i += 1
for i in range(1, N):
sg[i] = mex(i)
nim = 0
for item in L:
nim ^= sg[item]
if nim == 0:
print('lose')
else:
print('win')