0459 翻转游戏
* * * *
拉格朗日计划
* * * *
翻转游戏

翻转游戏由两名玩家在一块$N\times N$的方格板上进行,每个方格上放置着一个圆盘,圆盘一面黑一面白。

游戏开始时,所有的圆盘都是白面朝上。每一轮中当前玩家都可以选择满足如下所有条件的一个长方形区域,并翻转其中所有圆盘:

(1)该长方形区域的右上角是一白色圆盘。

(2)该长方形区域的宽度是完全平方数。

(3)该长方形区域的高度是一个三角数 (第k个三角数是1到k之和)。



两名玩家轮流操作,当某一玩家将所有的方格都翻转成黑色向上时即获胜。

令$W(N)$为在$N\times N$的方格板上游戏时能使先手玩家最终获胜的第一手操作的总数(称为必胜手)。

可以验证$W(1)=1, W(2)=0, W(5)=8, W(10^2)=31395$。其中$N=5$时的八种必胜手分别是:



求$W(10^6)$。

本题难度:



解答

本题涉及的Nim游戏及其理论本人不了解,以下解答和代码摘译改编自官方论坛:

本题需要计算每个局面的Nim值,把每个局面理解成$n\times n$位的二进制数,则可以观察到两个不同局面的Nim值是它们的异或和,因此一个局面的异或值可以理解为是一系列只有单个白色圆盘的局面的异或和。

把$(x,y)$处有单个白圆盘的局面称为局面$(x,y)$,则可以进一步注意到其Nim值等于局面$(x,1)$和局面$(1,y)$的Nim积,如此就将问题归约到了边界上的一维问题,从而可以进一步用异或和计算。

最终结果是$3996390106631$。

注:因使用cache装饰器作记忆化搜索,以下代码为Python 3,递归实现难以打印进度,运行约需一小时。

import itertools
from functools import *
from collections import Counter

N=10**6

def mex(it):
    s=set(it)
    return next(i for i in itertools.count() if i not in s)

@cache
def nimProd(i,j):
    if i&~-i: return nimProd(i&~-i,j)^nimProd(i&-i,j)
    if j&~-j: return nimProd(i,j&~-j)^nimProd(i,j&-j)
    return mex(nimProd(a,b)^nimProd(a,j)^nimProd(i,b) for a in range(i) for b in range(j))

def edge(f):
    vals=lambda mx: itertools.takewhile(lambda v: v <= mx, map(f, itertools.count(1)))
    s=[0]
    s.extend(s[-1]^mex(s[-1]^s[x-w] for w in vals(x)) for x in range(1,N+1))
    return s[-1],Counter(s[x]^s[x-w] for w in vals(N) for x in range(w,N+1))

A,As=edge(lambda w:w*w)
B,Bs=edge(lambda h:h*(h+1)//2)
target=nimProd(A,B)

print(sum(ac*bc for a,ac in As.items() for b,bc in Bs.items() if target==nimProd(a,b)))