博弈论

前言

当你的对手知道了你的决定之后,就能做出对自己最有利的决定

前提是你得知道怎么样才是最优的……


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;
}
posted @ 2021-09-11 06:55  Nickle-NI  阅读(104)  评论(0编辑  收藏  举报