博弈论

博弈论

奇异局势: 面对此局势, 不管做出任何动作, 都将输掉最终比赛.

巴什博奕(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), 非奇异局势可以通过一次操作达到奇异局势, k代表第k个, 也正好是b-a, 而奇异局势不能通过一次操作来得到另一个奇异局势.

局势取法
a==b2堆同取a个
a==ak且b>bkb堆取b-bk个
a==ak且b小于bk2堆同取a-aj(j==b-a)个
a>ak且b==bka堆取a-ak个
a==aj(j小于k)小于ak且b==bkb堆取b-bj个
a==bj(j小于k)小于ak且b==bkb堆取-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函数.*
python code

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出现奇异局势)
简化后的程序
python code

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值.*

python code

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')
点赞