[笔记]博弈
前
然而安装了chrome之后还是不能用typora
感觉博弈就是一种玄学
Nim博弈
总结
总结一下博弈里面经常用到的小技巧吧
- 考虑后手对先手的模仿—— POJ1740A New Stone Game
- Nim模型的各种扩展
- 巧妙地分割子问题
update 2019.11.16
窝以前都在干啥呀emmm
前言
还是暑假ZYX学长讲课的时候学的,然后现在除了异或已经愉快地忘记了QuuuuQ
于是简单地记一下吧
注意
- 不要闲得慌自己打输出的字符串,一个老年手抖就挂了
猴啦进入正文:
巴什博弈
从n个石子中取,每次只能取\(1-m\)个,不能取的人输
记\(n=k*(m+1)+r\)
- \(r==0\):不管先手怎么取,后手都可以使得n再次成为 \(m+1\)的倍数,直到n为0,所以后手必胜
- \(r!=0\):先手取\(r\)个,回到上一种情况,但在这个局面下,原本的先手已经变成了后手,所以先手必胜
例题
HDU1847
1、 总共n张牌;
2、 双方轮流抓牌;
3、 每人每次抓牌的个数只能是2的幂次(即:1,2,4,8,16…)
4、 抓完牌,胜负结果也出来了:最后抓完牌的人为胜者;
[解析]其实2的幂次是个幌子QuuuQ。如果n是3的倍数则先手必败
Nim游戏
有n堆石子,每次可以从任意一堆中取出任意个石子(1个以上),不能取的输
结论:当且仅当所有石子数的异或和为0时先手必败
证明:
-
若当前局面异或和为0,那么,先手不论取多少石子,取完之后后手总可以将异或和变为0,直到所有的数之和=异或和=0:
设当前异或和为\(k\),那么,必然存在至少一个\(a_i\),满足\(a_i\)的最高位和\(k\)的最高位(记为第\(j\)位)相同,那么,这时候只需要将\(a_i\)变成\(x=a_i \oplus k\), 就可以使得异或和重新变为0,。并且由于\(x\)的第\(j\)位以及更高为一定不为1,所以必然存在合法的操作
-
若当前的局面异或和不为0,那么先手可以进行一次操作,使得异或和为0,把必败状态留给后手
例题
SG函数
Part One .一些定义
记\(mex\){S}为整数集合S中最小的没有出现的非负整数
一个状态的SG函数为\(mex\){X},其中X是下一步所有状态的SG函数的集合
那么当且仅当当前状态的SG函数值为0时,当前状态必败
打个例子
比如说上面的HDU1847.
记\(SG(x)\)为当前有x张牌时的SG函数
当没有牌时,不能继续走了,所以后继状态的SG集合为空集,那么这时候的SG值为0,当有牌时,\(SG(x)=mex\){\(SG(x-1),SG(x-2)...,SG(x-2^k)\)},
- 如果SG(X)不为0,说明后继状态中必然出现了0,那么我们就可以转移到那个SG为0的状态使得对手必败。
- 否则,所有后继状态都没有出现0,即不存在一种方法使得对方必败,那么当前操作的人就必败了
代码
#include<bits/stdc++.h>
#define rep(X,A,B) for(int X=A;X<=B;X++)
#define tep(X,A,B) for(int X=A;X>=B;X--)
#define LL long long
const int N=1010;
using namespace std;
int n;
int g[10],vis[N],sg[N];
void INIT(){
g[0]=1;
rep(i,1,10)g[i]=g[i-1]*2;
sg[0]=0;
rep(i,1,1000){
rep(j,0,1000)vis[j]=0;
rep(j,0,10){
if(g[j]>i)break;
vis[sg[i-g[j]]]=1;
}
rep(j,0,1000){
if(!vis[j]){
sg[i]=j;
break;
}
}
}
}
int main(){
INIT();
while(scanf("%d",&n)==1){
if(sg[n]==0)printf("Cici\n");
else printf("Kiki\n");
}
return 0;
}
Part Two 游戏的SG函数
每一个游戏可以分成若干个子游戏,每一次可以选择其中一个子游戏进行操作,最后不能操作的人失败
对于每一个子游戏\(X_i\),都计算出SG值,那么整个游戏的SG值\(SG(X)=\oplus SG(x_i)\),即各子游戏的SG值的异或和。
证明:
实际上,我们熟悉的Nim游戏就是这样的一个例子:每一堆石子都是一个子游戏,并且有\(x\)个石子的一堆的\(SG(x)=x\)
那么道理就和Nim游戏一样,如果当前异或和为0,不管对方怎么操作,先手都可以把\(SG\)的异或和变为0 并最终把\(SG=0\)的必败局面留给对方
例题
Nim游戏的变形
Anti-Nim游戏
规则同Nim游戏,取走最后一个石子的人败
结论:先手必胜当且仅当:
-
所有堆都只有一个石子并且SG为0(即:只有偶数个1):
每个人每次只能取完一堆,最后一对必然是后手取的
-
至少有一个堆石子大于1并且SG不为0
证明:
-
SG不为0时:
- 至少有两堆大于1时,可以取走若干个,使得SG为0
- 只有1堆大于1时,先手可以将它取成0或1,从而决定剩下堆数奇偶性
-
SG为0时:
这不是必败的么OwO
例子
#include<cstdio>
#define rep(X,A,B) for(int X=A;X<=B;X++)
#define tep(X,A,B) for(int X=A;X>=B;X--)
#define LL long long
const int N=5000010;
using namespace std;
int SOLVE(){
int x,flg=0,now=0,n;
scanf("%d",&n);
rep(i,1,n){
scanf("%d",&x);
if(x>1)flg++;
now^=x;
}
if(flg==0&&now==0)return 1;
if(now&&flg>=1)return 1;
return 0;
}
int main(){
int T;
scanf("%d",&T);
while(T--){
if(SOLVE())printf("John\n");
else printf("Brother\n");
}
return 0;
}
阶梯Nim
n堆石子,每次可以将任意一堆中的任意个石子左移一堆(第一堆的直接移出去),移出的不能再操作,最后一个操作的人输
可以发现,先手在偶数位上的移动是没有意义的,因为后手总是可以将这些石子再一次移动到比它小的偶数位上并且最终移动到0位(出界)。那么就可以看成某一些石子移动到偶数位上就消失了,那么就变成了Nim游戏