Nim游戏及各种变形
Nim
\(n\) 堆物品,每堆 \(a_i\) 个,两个玩家轮流取走任意一堆的任意个物品,但不能不取,取走最后一个物品的人获胜。
Nim 和
定义 Nim 和 \(=a_i\oplus a_2 \oplus \cdots a_n\)
当且仅当 Nim 和为 \(0\) 时,先手必败,反则先手必胜。
证明
三个定理
- 没有后继状态的状态是必败状态。
- 一个状态是必胜状态当且仅当存在至少一个必败状态为它的后继状态。
- 一个状态是必败状态当且仅当它的所有后继状态均为必胜状态。
即需要证明
- \(a_1\oplus a_2\oplus \cdots \oplus a_n=0\) 的局面,一定不存在某种移动使得 \(a_1\oplus a_2\oplus \cdots \oplus a_n=0\)
- \(a_1\oplus a_2\oplus \cdots \oplus a_n \neq 0\) 的局面 ,一定存在某种移动使得 \(a_1\oplus a_2\oplus \cdots \oplus a_n=0\)
对于2,设 \(a_1\oplus a_2\oplus \cdots \oplus a_n=k\neq 0\) ,使得异或和为 \(0\) ,则需要将 \(a_i\) 改为 \(a_i'\) ,使得 \(a_i'=a_i \oplus k\)
根据异或的定义,有奇数个 \(a_i\) 在 \(k\) 在二进制下的最高位为 \(1\) 。因此满足这个条件的 \(a_i>a_i\oplus k\) (因为 \(a_i\oplus k\) 最高位变成 \(0\)) ,因此也满足石子越取越少的条件。
对于1,假设将 \(a_i\) 改为 \(a_i'\) 异或和仍为 \(0\) ,根据异或的运算性质可以得出 \(a_i=a_i'\) ,不满足条件。
阶梯 Nim
有 \(n\) 堆石子,每堆石子的数量为 \(x_{1},x_{2},\dots,x_{n}\),\(A,B\) 轮流操作,每次可以选第 \(k\) 堆中的任意多个石子放到第 \(k-1\) 堆中,第 \(1\) 堆中的石子可以放到第 \(0\) 堆中,最后无法操作的人为输。问 \(A\) 先手是否有必胜策略。
结论:先手必败当且仅当奇数堆中的石子数异或和为 \(0\) 。
证明
先手选择将奇数堆的 nim 和变为 \(0\) 。如果不能则已经必败。
此后如果后手移动奇数堆,则将奇数堆的 nim 和重新变为 \(0\) 。如果移动的是偶数堆,则将后手移动到奇数堆的石子继续往下移。
经过多次操作总能保证奇数堆处于必胜状态,最后先手总可以在后手之后将石子从奇数堆移动到偶数堆,最后移动到 \(0\) 堆。这样对手就不能移动了。
Anti Nim
\(n\) 堆物品,每堆 \(a_i\) 个,两个玩家轮流取走任意一堆的任意个物品,但不能不取,取走最后一个物品的人 失败。
结论
- 所有堆的石子数均为 \(1\) 且有偶数堆,先手必胜
- 至少有一堆石子个数大于 \(1\) 且 \(nim\) 和 \(\neq 0\) ,先手必胜。
- 否则先手必败
证明
给出一种不形式化的证明(太菜了)
- 所有石子数量均为 \(1\),则此时博弈树确定,偶数堆必胜,奇数堆必败
- 当有一堆石子数 \(>1\) 此时 \(nim\neq 0\)(根据异或分析)。
- 石子堆数为奇数,则把大于 \(1\) 的一堆取到 \(1\) 个石子,给后手情况 1 的奇数情况,后手必败。
- 石子堆数为偶数,把大于 \(1\) 的一堆选完,给后手情况 1 的奇数情况,后手必败。
- 当有两堆及以上的石子 \(>1\)。
- \(nim=0\),只能转移到 \(nim\neq 0\) 的状态。如果先手取完后只有一堆石子 \(>1\),则变为情况2;若果先手取完后仍有多堆石子大于 \(1\),则变为情况3.2。对于后手来说均必胜,则先手必败。
- \(nim\neq 0\),可以转移到 \(nim=0\) 的情况,即情况3.1。根据博弈论的基本定理,可以得出此时先手必胜。
例题
基础变形
LG1247 取火柴游戏
一个 nim 游戏,如果先手必胜则输出在第一步应在第 \(i\) 堆取 \(x\) 个石子(\(i\) 尽可能小),否则输出
lose
。(和原题有出入)。
先求出 nim 和 \(k\),然后顺着扫,如果 \(a_i>a_i\oplus k\) 则在这个位置取 \(a_i-(a_i\oplus k)\) 个。如果没有这样的 \(i\) ,则 lose
。
for(int i=1;i<=n;++i){
a[i]=read();
nim^=a[i];
}
for(int i=1;i<=n;++i){
if(a[i]>(a[i]^nim)){
ans=i,minv=a[i]-(a[i]^nim);
break;//使得ans尽可能小
}
}
LG7589 黑白棋
一个 nim 游戏,双方还可以选择每次给每堆石子 \(+d\) 个,每人可以添加 \(k\) 次。问先手能否获胜。
发现一旦先手必胜,后手每添加 \(d\) 个,先手都可以选择减少 \(d\) 个,而添加次数有限,所以先手必胜。后手同理。
代码略。
进阶
LG5675 [GZOI2017]取石子游戏
Alice 和 Bob 玩 nim 游戏
现在场地上有\(N\)堆石子,编号为 \(1\) 至 \(N\)。Alice 很快发现了这个游戏存在一些固定的策略。阴险的 Alice 想赢得这场比赛就来找到主办方你,希望你在这 \(N\) 堆石子中选出若干堆石子作为最后游戏用的石子堆并使得 Alice 能获得胜利。你自然不想让 Alice 得逞,所以你提出了一个条件:Alice 在这个游戏中第一次取的那堆石子的编号需要你来指定(仅指定取的石子堆编号,不指定第一次取多少个,这个指定的石子堆必然包含在最后游戏用的石子堆中)。
现在你很好奇,你想算算有多少种方案让 Alice 不能获胜。注意,即使选出的石子堆的编号的集合完全相同,指定第一次取的石子堆的编号不同,也认为方案是不同的。方案数 \(\bmod 10^9+7\) 。\(N\leq 200,a_i\leq 200\) 。
两种情况下先手无法取胜
- 所有堆石子 nim和 \(=0\) 。
- 指定一堆先手必须选,这一堆选任意石子都无法使 nim和 \(=0\) 。
现在题目要求选出来的石子堆必能先手获胜。考虑如何满足情况2。
假设第 \(i\) 堆石子取 \(k\) 个后 nim 和为 0 。设其他堆的异或和为 \(b\) 。则
因为不能使得 nim 和为 0 ,所以自然的 \(a_i-b\leq 0\)
这时问题就转化为了求不取第 \(i\) 堆石子,其他堆任意选的异或值大于等于 \(a_i\) 的方案数。
const ll MOD=1000000007;
int n,a[N];
ll dp[N][256],ans;//异或后值会大于200
int main(){
n=read();
for(int i=1;i<=n;++i)a[i]=read();
for(int i=1;i<=n;++i){//枚举必须选哪一堆石子
dp[0][0]=1ll;
for(int j=1;j<=n;++j){
for(int k=0;k<256;++k){
if(i==j)dp[j][k]=dp[j-1][k];
else dp[j][k]=(dp[j-1][k]+dp[j-1][k^a[j]])%MOD;
}
}
for(int k=a[i];k<256;++k)
ans=(ans+dp[n][k])%MOD;
}printf("%lld\n",ans);
return 0;
}
阶梯 Nim
LG3480 [POI2009]KAM-Pebbles
有 \(n\) 堆石子,除了第一堆外,每堆石子个数都不少于前一堆的石子个数。两人轮流操作每次操作可以从一堆石子中移走任意多石子,但是要保证操作后仍然满足初始时的条件谁没有石子可移时输掉游戏。问先手是否必胜。
定义数组 \(b_i=a_{i+1}-a_{i}\) 。根据题意,无论如何操作, \(\forall i,b_i\geq 0\)
发现对于 \(a_i\) ,减去 \(k\) 后,\(b_i\gets b_i+k,b_{i+1}\gets b_{i+1}-k\) 。
这等价于一个反向的阶梯nim。即 \(b_{i-1}\) 为阶梯 nim 的第一个数。
具体见代码。
int n,a[N],nim;
int main(){
int T=read();
while(T--){
n=read(),nim=0;
for(int i=1;i<=n;++i)
a[i]=read();
for(int i=1;i<=n;++i)
if((n-i+1)&1)nim^=(a[i]-a[i-1]);
puts(nim?"TAK":"NIE");
}
return 0;
}
LG2575 高手过招
有一张 \(n\times 20\) 的棋盘,上面摆放了一些棋子。对于一个棋子,能将它向右移动一格,如果右边有棋子,则向右跳到第一个空格,如果右边没有空格,则不能移动这个棋子,如果所有棋子都不能移动,那么将输掉这场比赛。
每行状态是互相独立的,现在只需要求出每行的 \(SG\) 函数,最后亦或起来。
发现在一行钟,空格总数不变,把空格看成分解,两个空格之间间隔的旗子看作石子,发现每堆石子只能向更右边的一堆石子移动,最右边的石子不能移动。这是问题就转化成了阶梯 nim 。注意最右的棋子堆的编号为 \(0\) 。
inline int stepnim(){
memset(vis,0,sizeof(vis));
int m=read(),nim=0;
for(int i=1;i<=m;++i){
int x=read();
vis[x]=1;
}
for(int i=20,tot=0,cnt=0;i>=0;--i){//cnt记录是第几堆,最右边的实质上是第0堆
if(!vis[i]){
if(cnt&1)nim^=tot;
tot=0;++cnt;
}else ++tot;
}
return nim;
}
//...
n=read(),ans=0;
for(int i=1;i<=n;++i){
int tmp=stepnim();
ans^=tmp;
}
puts(ans?"YES":"NO");
同时这题范围很小,还可以 \(2^{20}\times 20\) 地预处理所有状态的 \(SG\) 函数。
K-Nim
有 \(n\) 堆石子,其中第 \(i\) 堆有 \(a_i\) 颗,每次可以取走 \(1\) 至 \(k\) 堆石子中任意颗石子,不能操作者输。
结论
记数 \(x\) 二进制下的第 \(i\) 位表示为 \((x)_i\) 。若
则先手必败,反之先手必胜。
证明
- 终止状态为必败态 (\(P\) 态)
所有堆石子为 \(0\) ,满足。
- 所有的 \(P\) 态只能转移到 \(N\) 态 (参考的题解这里写反了)
因为最多只能操作 \(k\) 堆石子,而使得 \(\bmod (k+1)\) 为 \(0\) 变成 \(0\) ,至少需要操作 \((k+1)\) 次。所以 \(P\) 态不能转移到 \(P\) 态,而只能转化到 \(N\) 态。
- 所有的 \(N\) 态可以转化到 \(P\) 态
记 \(b_s=(\sum_{i=1}^n(a_i)_s)\bmod(k+1)\) ,则 \(b_s\in(0,k]\)
只需要找到 \(b_s\) 堆石子,从中取走 \(2^s\) 颗石子即可转移到 \(P\) 态。
例题
LG2490 [SDOI2011]黑白棋
小 A 和小 B 又想到了一个新的游戏。
这个游戏是在一个 \(1 \times n\) 的棋盘上进行的,棋盘上有 \(k\) 个棋子,一半是黑色,一半是白色。
最左边是白色棋子,最右边是黑色棋子,相邻的棋子颜色不同。
小 A 可以移动白色棋子,小 B 可以移动黑色的棋子,其中白色不能往左,黑色不能往右。他们每次操作可以移动 \(1\) 到 \(d\) 个棋子。
每当移动某一个棋子时,这个棋子不能跨越两边的棋子,当然也不可以出界。当谁不可以操作时,谁就失败了。
小 A 和小 B 轮流操作,现在小 A 先移动,有多少种初始棋子的布局会使他胜利呢?
棋子不能相互跨越,则最后 \(\frac{k}{2}\) 对相邻的黑白棋子必定会被放置在相邻的两个格子中,可以将相邻的 \(2\) 颗棋子之间的距离看作一堆石子的颗数,问题转化为了 \(\text{d-Nim}\) 问题 。
假设 \(dp_{i,j}\) 为各数的第 \(0\sim i-1\) 个二进位上的数的和都为 \(0\) 、并且用掉了 \(j\) 个位置的方案数。这是先手必败的方案,用 \(\binom{n}{k}\) 减去即可。
假设新的一位上各数的和为 \(t\times(d+1)\) ,那么方案数即为 \(\dbinom{\frac{k}{2}}{t\times (d+1)}\) 。转移就是
最后的方案就是
其中 \(maxl\) 表示 \(n-k\) 的二进制位数(具体是多少无所谓,数量级正确即可)。二项式的组合意义是:确定了多少个位置可以选和每对棋子的一个端点后,剩下的端点该如何选取。
dp[0][0]=1;
for(ll i=0;i<=16;++i){
for(ll j=0;j<=n-k;++j){
for(ll t=0;t*bas2[i]*(d+1)<=n-k&&t*(d+1)<=k/2;++t)
dp[i+1][j+t*bas2[i]*(d+1)]=(dp[i+1][j+t*bas2[i]*(d+1)]+dp[i][j]*C(k/2,t*(d+1))%MOD)%MOD;
}
}
for(ll j=0;j<=n-k;++j)
ans=(ans+dp[17][j]*C(n-k/2-j,k/2)%MOD)%MOD;
printf("%lld\n",((C(n,k)-ans)%MOD+MOD)%MOD);//别忘了ans是不合法的个数
Fibonacci Nim
有 \(n\) 枚石子。两位玩家定了如下规则进行游戏:
- Mirko 先取一次,Slavko 再取一次,然后 Mirko 再取一次,两人轮流取石子,以此类推;
- Mirko 在第一次取石子时可以取走任意多个;
- 接下来,每次至少要取走一个石子,最多取走上一次取的数量的 \(2\) 倍。当然,玩家取走的数量必须不大于目前场上剩余的石子数量。
- 取走最后一块石子的玩家获胜。
双方都以最优策略取石子。Mirko 想知道,自己第一次至少要取走几颗石子最终才能够获胜。
感觉和 Nim 游戏没什么关联。
齐肯多夫(Zeckendorf)定理
任何正整数都可以表示成若干个不连续的斐波那契数之和。
证明:
-
若 \(n\) 为斐波那契数,根据斐波那契的定义可以比较容易得构造出来。
-
若 \(n\) 非斐波那契数
取 \(fib_i\) ,满足 \(fib_i<n<fib_{i+1}\) 。
另 \(n'=n-fib_i\) ,取 \(fib_j<n'<fib_{j+1}\)
需要证明 \(i\neq j+1\)
假设 \(i=j+1\) ,则 \(fib_{i+1}=fib_{i}+fib_{j}=n\) ,又 \(n<fib_{i+1}\) ,所以假设不成立。
解法
如果 \(n\) 为斐波那契数,则先手必败
证明:
\(n=fib_i=fib_{i-1}+fib_{i-2}\)
- 如果先手取大于 \(fib_{i-2}\) 个数,则后手可以第二次把所有数取完。因为 \(fib_{i-2}\times 3>fib_i\) 。
- 如果先手取小于 \(fib_{i-2}\) 个数,问题转化为了更小范围的子问题。假如结论成立,那么后手总有办法取掉这 \(fib_{i-2}\) 的最后一个数,使得先手取 \(fib_{i-1}\) 个数,先手仍然必败。结论自洽。(实在不知道怎么说明了)
如果正整数 \(n\) 不为斐波那契数,则将其用齐肯多夫表示法表示后,最小的那一堆个数即为答案。
\(n=f_1+f_2+\cdots+f_k\)
其中 \(f\) 为互不相邻的斐波那契数。
先手取走 \(f_k\) 个数后,易证 \(f_{k-1}>2\times f_k\) ,所以后手无法一次性取完 \(f_{k-1}\) 个数,又 \(f\) 都是斐波那契数,所以后手一定有办法取掉这 \(f_{k-1}\) 个数的最后一个数。最后先手一定取 \(f_1\) 个数,所以先手必败。
ll fib[1003],x;int tot=2;bool vis[1003];
int main(){
fib[0]=1,fib[1]=1;
while(1){
fib[tot]=fib[tot-1]+fib[tot-2];
if(fib[tot]>=1e17)break;
++tot;
}
scanf("%lld",&x);
for(int i=tot;i>=1;--i){
if(!vis[i+1]&&fib[i]<=x){
x-=fib[i];
if(!x)return printf("%lld\n",fib[i]),0;
}
}
return 0;
}
本文作者:BigSmall_En
本文链接:https://www.cnblogs.com/BigSmall-En/p/nim.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步