博弈论
前言
当你的对手知道了你的决定之后,就能做出对自己最有利的决定
前提是你得知道怎么样才是最优的……
nim游戏
复杂,是博弈的一种性质
上面那句话我也不知道谁说的。
模板
nim游戏的基本模型如下:
题目
甲,乙两个人玩 \(nim\) 取石子游戏。
\(nim\) 游戏的规则是这样的:地上有 \(n\) 堆石子(每堆石子数量小于 \(10^4\)),每人每次可从任意一堆石子里取出任意多枚石子扔掉,可以取完,不能不取。每次只能从一堆里取。最后没石子可取的人就输了。假如甲是先手,且告诉你这 \(n\) 堆石子的数量,他想知道是否存在先手必胜的策略。
思路
在此为大家隆重介绍:《 O I 圣 经 》
据《 O I 圣 经 》记载,
-
当所有石子数量为 \(0\) 时,先手必输。(显然),此时异或和为 \(0\)
-
如果所有的石子堆的异或和为 \(0\) , 先手必输。
感性理解:
考虑一个取石子的策略,使你的对手面对的是 “石子数量为 \(0\) ” 这个局面。
想到,保持一个局面:你取完之后,对方面对的是所有石子堆得异或和为\(0\) 。由于对方不能不取,所以对方无论怎么取,最终的异或和都不为 \(0\) ,接下来又转移到你的回合,你再次使对方的异或和为 \(0\) ,不断重复此过程直到最后,对方总会面对“石子数量为 \(0\) ” 这个局面。
同理,对方的最优策略也是这样。因此可以得到,如果石子堆的异或和为 \(0\) 先手必败。
code
根据意思写代码。
#include<bits/stdc++.h>
using namespace std;
int T,n,x,s;
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);s=0;
while(n--){
scanf("%d",&x);
s^=x;
}
if(s==0)printf("No\n");
else printf("Yes\n");
}
return 0;
}
扩展:黑白棋
例题: [GZOI2017]取石子游戏
题目
描述
\(Alice\) 和 \(Bob\) 在玩一个古老的游戏。现在有若干堆石子,\(Alice\) 和 \(Bob\) 轮流取,每次可以选择其中某一堆的石子中取出任意颗石子,但不能不取,谁先取完使得另一个人不能取了算赢。
现在场地上有 \(N\) 堆石子,编号为 \(1\) 至 \(N\)。\(Alice\) 很快发现了这个游戏存在一些固定的策略。阴险的 \(Alice\) 想赢得这场比赛就来找到主办方你,希望你在这 \(N\) 堆石子中选出若干堆石子作为最后游戏用的石子堆并使得 \(Alice\) 能获得胜利。你自然不想让 \(Alice\) 得逞,所以你提出了一个条件:
\(Alice\) 在这个游戏中第一次取的那堆石子的编号需要你来指定(仅指定取的石子堆编号,不指定第一次取多少个,这个指定的石子堆必然包含在最后游戏用的石子堆中)。
现在你很好奇,你想算算有多少种方案让 \(Alice\) 不能获胜。注意,即使选出的石子堆的编号的集合完全相同,指定第一次取的石子堆的编号不同,也认为方案是不同的。
输入格式
第一行,一个正整数 \(N\),表示石子堆数。
第二行,\(N\) 个正整数,表示各堆石子的数量,按编号 \(1\) 至 \(N\) 依次给出。
输出格式
一行,表示方案数。答案对 \(10^9+7\)
思路
和 \(nim\) 游戏模板类似。如果想使先手必败有以下两种情况:
-
异或和为0
-
异或和不为 \(0\) .但是不能转移到异或和为 \(0\) 。
在本题中,可以指定选择的第一堆石子。假设这堆石子的数量为 \(a_1\),剩下其他的石子的异或和为 \(A\)。
想选择一个数 \(x\) ,使得 \((a_1-x) \oplus A = 0\) , 其中 \(1 \leq x\leq a_1\) 。
即 \(a_1-x=A\)
那么有 \(a_1=A+x \Rightarrow a_1\geq A+1\)
如果想使先手必输,那么第一堆石子的数量要小于等于其他石子堆的异或和。
定义 \(dp[i][j]\) 为前 \(i\) 堆除了枚举的第 \(k\) 堆外,异或起来的数为 \(j\) 的方案数。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=1e9+7;
const int N=305;
int n,a[N],dp[N][N],ans;
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return x*f;
}
signed main(){
n=read();
for(int i=1;i<=n;i++) a[i]=read();
dp[0][0]=1;
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++)for(int j=0;j<=255;j++)
if(k==i) dp[i][j]=dp[i-1][j];
else dp[i][j]=(dp[i-1][j]+dp[i-1][j^a[i]])%mod;
for(int i=a[k];i<=255;i++)ans+=dp[n][i],ans%=mod;
}
printf("%lld",ans);
return 0;
}