博弈论略解

博弈论

我们把动物利用大自然移动的瘾魂,在决策人期待的空间里,形成三维均衡的语文学理论,称为博弈论。博弈论是二人在平等的对局中各自利用对方的策略变换自己的对抗策略,达到取胜的目的。

题目描述 loj10241 \text{loj10241} loj10241

有一种有趣的游戏,玩法如下:

玩家: 人;
道具: N N N 颗石子;
规则:游戏双方轮流取石子;每人每次取走若干颗石子(最少取 1 1 1 颗,最多取 K K K 颗);石子取光,则游戏结束;最后取石子的一方为胜。
假如参与游戏的玩家都非常聪明,问最后谁会获胜?

输入格式

输入仅一行,两个整数 N N N K K K

输出格式

输出仅一行,一个整数,若先手获胜输出 1 1 1,后手获胜输出 2 2 2

样例输入

23 3

样例输出

1

数据范围与提示

对于全部数据, 1 ≤ K ≤ N ≤ 1 0 5 1\leq K\leq N\leq 10^5 1KN105

Solution 10241 \text{Solution 10241} Solution 10241

依题意得,在石子足量时,每次可以取的数量为集合
A = { 1 , 2 , 3 , . . . , K − 2 , K − 1 , K } A=\{1,2,3,...,K-2,K-1,K\} A={1,2,3,...,K2,K1,K}中的任一元素。不难发现,首末第 1 1 1 对数之和、首末第 2 2 2 对数之和、 ⋅ ⋅ ⋅ ··· ,它们的值都相等且都等于 K + 1 K+1 K+1。所以,对于后手玩家取的石头数,先手玩家总有一种取法使先后手玩家一回合内取的石头总和为 K + 1 K+1 K+1
所以,第一回合先手只需要将石头取至 K + 1 K+1 K+1 的倍数,就一定能取到最后一颗石头。

#include<cstdio>
#include<cstdlib>
#include<cstring>

#define reg register

int n,k;

int main(){
	scanf("%d%d",&n,&k);
	printf((n%(k+1))?"1":"2");
}

题目描述 loj10242 \text{loj10242} loj10242

有一种有趣的游戏,玩法如下:

玩家: 人;
道具: N N N 堆石子,每堆石子的数量分别为 X 1 , X 2 , ⋅ ⋅ ⋅ , X n X_1,X_2,···,X_n X1,X2,,Xn
规则:

  1. 游戏双方轮流取石子;
  2. 每人每次选一堆石子,并从中取走若干颗石子(至少取 1 1 1 颗);
  3. 所有石子被取完,则游戏结束;
  4. 如果轮到某人取时已没有石子可取,那此人算负。

假如两个游戏玩家都非常聪明,问谁胜谁负?

输入格式

第一行,一个整数 N N N
第二行, N N N 个空格间隔的整数 X i X_i Xi,表示每一堆石子的颗数。
输出格式
输出仅一行,一个整数,若先手获胜输出 win,后手获胜输出 lose。

样例输入

4
7 12 9 15

样例输出

win

数据范围与提示

对于全部数据, N ≤ 5 × 1 0 4 , 1 ≤ X i ≤ 1 0 5 N\leq5\times10^4,1\leq X_i\leq10^5 N5×104,1Xi105

Solution 10242 \text{Solution 10242} Solution 10242

为记述简便,我们用 ( { X i } ) (\{X_i\}) ({Xi}) 表示剩余石头状态。
先考虑只有 2 2 2 组石头的情况,容易得到

状态 ( 0 , 0 ) (0,0) (0,0) ( 1 , 0 ) (1,0) (1,0) ( 1 , 1 ) (1,1) (1,1) ( 2 , 1 ) (2,1) (2,1) ( 2 , 2 ) (2,2) (2,2)
先手

也就是说,当两堆石头数量一样时,先手必胜;否则后手必胜。
证明: ( k , k )   ( k &gt; 0 ) (k,k)\ (k&gt;0) (k,k) (k>0)时,先手取 k k k 颗石头时,后手在另一堆中也取 k k k 颗石头,此时两堆石头数量相等。所以,当先手有石头可取时,后手一定有石头可取,所以后手必胜。 Q.E.D.. \text{Q.E.D..} Q.E.D..

一般地,我们把满足

  1. 有两名选手;
  2. 两名选手交替对游戏进行移动,每走一步,选手可以在(一般而言)有限的合法移动集合中任选一种进行移动;
  3. 对于游戏的任何一种可能的局面,合法的移动集合只取决于这个局面本身,不取决于轮到哪名选手操作、以前的任何操作、骰子的点数或者其它什么因素;
  4. 如果轮到某名选手移动,且这个局面的合法的移动集合为空(也就是说此时无法进行移动),则这名选手负。

的模型叫做公平组合游戏

如果一状态下先手玩家无法获胜,则称该状态为必败态;类似地,如果一状态下先手玩家可以使后手玩家无法获胜,则称该状态为必胜态

根据定义,我们有:

  1. 若一状态下,当前玩家无合法移动方案(即:合法方案集为空),则该状态是必败态;
  2. 若一状态是必败态,则它在当前玩家一合法移动后变成必胜态;
  3. 若一状态是必胜态,则它在当前玩家一合法移动后变成必败态。

回到刚刚的问题。那如果有 3 3 3 堆或更多石头呢?显然我们不能继续枚举下去,我们需要一条适用范围更广的结论。

对于 ( { X i } ) (\{X_i\}) ({Xi}),若
X 1 ⊕ X 2 ⊕ ⋅ ⋅ ⋅ ⊕ X n = 0 X_1\oplus X_2\oplus···\oplus X_n=0 X1X2Xn=0则该状态为必败态,其中 ⊕ \oplus 表示异或。
证明: 根据定义,当前状态为必败态需满足以下其中一条:

  1. 合法方案集为空;
  2. 一合法移动后得到的所有状态均为必胜态;
  3. 该状态由必胜态得到。

满足情况1的充要条件是
X 1 = X 2 = ⋅ ⋅ ⋅ = X n = 0 X_1=X_2=···=X_n=0 X1=X2==Xn=0此时结论显然成立。

情况2:对于一状态 ( { X i } ) (\{X_i\}) ({Xi}),若 X 1 ⊕ X 2 ⊕ ⋅ ⋅ ⋅ ⊕ X n ≠ 0 X_1\oplus X_2\oplus···\oplus X_n\neq0 X1X2Xn̸=0,则一定存在一个合法方案:将 X i X_i Xi 变成 X i ′ X&#x27;_i Xi,使 X 1 ⊕ X 2 ⊕ ⋅ ⋅ ⋅ ⊕ X i − 1 ⊕ X i ′ ⊕ X i + 1 ⊕ ⋅ ⋅ ⋅ ⊕ X n = 0 X_1\oplus X_2\oplus···\oplus X_{i-1}\oplus X&#x27;_i\oplus X_{i+1}\oplus ···\oplus X_n=0 X1X2Xi1XiXi+1Xn=0。不妨设 X X X 的异或和为 k k k,找出二进制下,在 k k k 的有效最高位为 1 1 1 X i X_i Xi,改变 X i X_i Xi 使 k = 0 k=0 k=0 即可。

情况3:由于满足 a ⊕ c = b ⊕ c ,   a , b , c ∈ Z a\oplus c=b\oplus c,\ a,b,c\in \Z ac=bc a,b,cZ 的充要条件是 a = b a=b a=b,所以当一状态为必胜态时, k = 0 k=0 k=0,一定不存在合法方案使 k ′ = 0 k&#x27;=0 k=0。换句话说,当 a ≠ b a\neq b a̸=b时,它们异或一个相同的数的值一定不等,所以 k ′ ≠ 0 k&#x27;\neq0 k̸=0 Q.E.D.. \text{Q.E.D..} Q.E.D..

#include<cstdio>
#include<cstdlib>
#include<cstring>

#define reg register

int n,a,ans=0;

int main(){
	scanf("%d",&n);
	for(reg int i=1;i<=n;++i){
		scanf("%d",&a);
		ans^=a;
	}
	puts(ans?"win":"lose");
}

题目描述 loj10243 \text{loj10243} loj10243

给定一个有 N N N 个节点的有向无环图,图中某些节点上有棋子,两名玩家交替移动棋子。
玩家每一步可将任意一颗棋子沿一条有向边移动到另一个点,无法移动者输掉游戏。
对于给定的图和棋子初始位置,双方都会采取最优的行动,询问先手必胜还是先手必败。

输入格式

第一行,三个整数 N , M , K N,M,K N,M,K N N N 表示图中节点总数, M M M 表示图中边的条数, K K K 表示棋子的个数。
接下来 M M M 行,每行两个整数 X , Y X,Y X,Y 表示有一条边从 X X X 出发指向 Y Y Y
接下来一行, K K K 个空格间隔的整数,表示初始时,棋子所在的节点编号。

输出格式

若先手胜,输出 win,否则输出 lose。

样例输入

6 8 4
2 1
2 4
1 4
1 5
4 5
1 3
3 5
3 6
1 2 4 6

样例输出

win

数据范围与提示

对于全部数据, N ≤ 2000 , M ≤ 6000 , 1 ≤ K ≤ N N\leq2000,M\leq6000,1\leq K\leq N N2000,M6000,1KN

Solution 10243 \text{Solution 10243} Solution 10243

对于出度为 0 0 0 的点,它们的 s g sg sg 值为 0 0 0;对于出度不为 0 0 0 的点,它们的后继状态就是它们的儿子节点的 s g sg sg 值。

#include<cstdio>
#include<cstdlib>
#include<cstring>

#define reg register

struct node{
	int x,y,next;
}e[6010];
int len=0;
int n,m,k;
int s1,s2;
bool lf[2010];
int sg[2010];
int first[2010];
int ans=0,a;
bool vis[2010];

void ins(int x,int y){
	e[++len].x=x;e[len].y=y;lf[x]=0;
	e[len].next=first[x];first[x]=len;
}
int dfs(int x){
	if(lf[x]) return 0;
	if(vis[x]) return sg[x];
	vis[x]=1;
	bool bk[2010]={0};
	for(reg int i=first[x];i;i=e[i].next){
		int y=e[i].y;
		sg[y]=dfs(y);
		bk[sg[y]]=1;
	}
	for(reg int i=0;i<=n+10;++i)
		if(!bk[i]) return i;
}
int main(){
	memset(lf,1,sizeof(lf));
	scanf("%d%d%d",&n,&m,&k);
	for(reg int i=1;i<=m;++i){
		scanf("%d%d",&s1,&s2);
		ins(s1,s2);
	}
	for(reg int i=1;i<=n;++i)
		sg[i]=dfs(i);
	for(reg int i=1;i<=k;++i){
		scanf("%d",&a);
		ans^=sg[a];
	}
	puts(ans?"win":"lose");
}

题目描述 loj10244 \text{loj10244} loj10244

原题来自:BeiJing 2009 WC

小 H 和小 Z 正在玩一个取石子游戏。取石子游戏的规则是这样的,每个人每次可以从一堆石子中取出若干个石子,每次取石子的个数有限制,谁不能取石子时就会输掉游戏。小 H 先进行操作,他想问你他是否有必胜策略,如果有,第一步如何取石子。

输入格式

第一行为石子的堆数 N N N
接下来 N N N 行,每行一个数 A i A_i Ai,表示每堆石子的个数,接下来一行为每次取石子个数的种类数 。
接下来 M M M 行,每行一个数 B i B_i Bi,表示每次可以取的石子个数,输入保证这 M M M 个数按照递增顺序排列。

输出格式

第一行为 YES 或者 NO,表示小 H 是否有必胜策略。
若结果为 YES,则第二行包含两个数,第一个数表示从哪堆石子取,第二个数表示取多少个石子,若有多种答案,取第一个数最小的答案,若仍有多种答案,取第二个数最小的答案。

输入样例

4
7
6
9
3
2
1
2

输出样例

YES
1 1

数据范围与提示

对于全部数据, N , M ≤ 10 , A i ≤ 1000 , B i ≤ 10 N,M\leq 10,A_i\leq 1000,B_i\leq 10 N,M10,Ai1000,Bi10

Solution 10244 \text{Solution 10244} Solution 10244

考虑函数
s g i = mhx { i − B j }   ( ∀ i ≥ B j ) sg_i=\text{mhx}\{i-B_j\}\ (\forall i\geq B_j) sgi=mhx{iBj} (iBj)其中 mhx { A } \text{mhx}\{A\} mhx{A} 表示不被 A A A 包含的最小非负整数。

那么,当 s g a i sg_{a_i} sgai 的异或和 k k k 0 0 0 时,先手必败;否则先手必胜。若 i ∈ [ 1 , N ] , j ∈ [ 1 , M ] i \in [1,N],j\in [1,M] i[1,N],j[1,M] 使 k ⊕ s g a i ⊕ s g a i − b j = 0 k \oplus sg_{a_i} \oplus sg_{a_i-b_j}=0 ksgaisgaibj=0 a i ≥ b j a_i\geq b_j aibj,那么第一步就在第 i i i 堆中取出 b j b_j bj 个石头。

#include<cstdio>
#include<cstdlib>
#include<cstring>

#define reg register

int n,m;
int a[20],b[20];
bool bucket[1010];
int ans=0;
int sg[1010];
int k=-1;

int main(){
	scanf("%d",&n);
	for(reg int i=1;i<=n;++i)
		scanf("%d",&a[i]);
	scanf("%d",&m);
	for(reg int i=1;i<=m;++i)
		scanf("%d",&b[i]);
	for(reg int i=1;i<=1000;++i){
		memset(bucket,0,sizeof(bucket));
		for(reg int j=1;j<=m;++j){
			if(i<b[j]) break;
			bucket[sg[i-b[j]]]=1;
		}
		for(reg int j=0;j<=1000;++j)
			if(!bucket[j]){
				sg[i]=j;
				break;
			}
	}
	for(reg int i=1;i<=n;++i)
		ans^=sg[a[i]];
	puts(ans?"YES":"NO");
	if(!ans) exit(0);
	for(reg int i=1;i<=n;++i)
		for(reg int j=1;j<=m;++j){
			if(a[i]-b[j]<0) break;
			if((ans^sg[a[i]]^sg[a[i]-b[j]])==0){
				printf("%d %d\n",i,b[j]);
				exit(0);
			}
		}
}

题目描述 loj10245 \text{loj10245} loj10245

原题来自:BZOJ 1299
TBL 和 X 用巧克力棒玩游戏。每次一人可以从盒子里取出若干条巧克力棒,或是将一根取出的巧克力棒吃掉正整数长度。TBL 先手两人轮流,无法操作的人输。他们以最佳策略一共进行了 10 10 10 轮(每次一盒)。你能预测胜负吗?

输入格式

输入数据共 20 20 20 行。第 2 i − 1 2i-1 2i1 行一个正整数 ,表示第 N i N_i Ni 轮巧克力棒的数目。第 2 i 2i 2i N N N 个正整数 L i , j L_{i,j} Li,j,表示第 i i i 轮巧克力棒的长度。

输出格式

输出数据共 10 10 10 行。每行输出 YES 或 NO,表示 TBL 是否会赢。如果胜则输出 NO,否则输出 YES。

样例输入

3
11 10 15 
5
13 6 7 15 3 
2
15 12 
3
9 7 4 
2
15 12 
4
15 12 11 15 
3
2 14 15 
3
3 16 6 
4
1 4 10 3 
5
8 7 7 5 12

样例输出

YES
NO
YES
YES
YES
NO
YES
YES
YES
NO

数据范围与提示

对于全部数据, N ≤ 14 , L ≤ 1 0 9 N\leq 14,L\leq10^9 N14,L109

Solution 10245 \text{Solution 10245} Solution 10245

对于一个合法移动,可以是

  1. 从盒中取巧克力棒;
  2. 吃取出的巧克力棒。

中的一种。对于一个状态,若盒中的状态是必胜态,盒外是必败态;或盒中的状态是必败态,盒外是必胜态;则该状态是必胜态。尝试把所有巧克力棒分成两堆,使一堆的异或和为 0 0 0,另一堆不为 0 0 0 即可。

#include<cstdio>
#include<cstdlib>
#include<cstring>

#define reg register

int n;
int a[20];
bool tf;

void dfs(int x,int nd,int w){
	if(x==n+1){
		if(!w&&nd)
			tf=1;
		return;
	}
	dfs(x+1,nd+1,w^a[x]);
	if(tf) return;
	dfs(x+1,nd,w);
}
int main(){
	for(reg int t=1;t<=10;++t){
		scanf("%d",&n);
		for(reg int i=1;i<=n;++i)
			scanf("%d",&a[i]);
		tf=0;dfs(1,0,0);
		puts(!tf?"YES":"NO");
	}
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值