硬币游戏 (博弈入门)
题目:
A 和 B 在玩一个游戏,给定K个数字a1,a2,a3...ak ; 一开始有x个硬币 ; A 和 B 轮流取硬币。每次所取硬币的枚数一定在a1,a2,a3..ak ; 里面,A 先取 , 取走最后一枚的获胜 , 两个人都采取最优的策略,谁会WIN ?
分析:
博弈论的关键就在于找出必胜态 和 必败态 ;
1.既然取关了硬币就是获胜 , 那就是说到自己的时候没有硬币了,就是输了 ; 则j=0 ; 时是必败态 ;
2.如果对于某个i(1<=i<=k) , j-ai是传说中的必败态 , 那 j 就是必胜态 ; (当只有j枚硬币的时候 , 只要拿走a1枚对手就输了,那就是等于我赢了)
3.如果对于任意的i(1<=i<=k) , j-ai 都是必胜态,j就是必败态 ; (无论怎么取都是对手赢,那就是输)
难以理解的话,可以把 j 看成是当前的状态 , j-ai 是上一个状态 ; 这样可以找到j
依靠这些规则,采用动态规划的思想按照j从小到大的顺序计算必胜与必败 ; 到时我们只要看x是必胜还是必败即可 ;
#include<stdio.h> int x,k,a[20001]; bool win[20001]; int main( ) { scanf("%d %d",&x,&k); for(int i=0 ; i<k ; i++) scanf("%d",&a[i]); win[0] = false;//到自己的时候没有了就输了; for(int j=1 ; j<=x ; j++) { win[j] = false; for(int i=0 ; i<k ; i++) { win[j] |=a[i]<=j && !win[j-a[i]]; } } if(win[x]) puts("A"); else puts("B"); return 0; }
自己敲的
#include<stdio.h> int x,k,a[20001]; bool win[20001]; int main( ) { scanf("%d %d",&x,&k); for(int i=0 ; i<k ; i++) scanf("%d",&a[i]); win[0] = false;//到自己的时候没有了就输了; for(int j=1 ; j<=x ; j++) { win[j] = false; for(int i=0 ; i<k ; i++) { if(a[i]<=j) { if(win[j-a[i]]==false) win[j]=true; } } } if(win[x]) puts("A"); else puts("B"); return 0; }
题目:
意思就是两个人轮流拿硬币,Alice先拿,Alice拿的时候可以选择拿走一个或者拿走相邻的两个,谁拿完最后的石子胜利。
#include<stdio.h> int main( ) { int n ; scanf("%d",&n); if(n<=2) puts("Alice"); else puts("Bob"); return 0; }
题目:
A 和 B 在玩游戏,给k个数字a1 , a2 , ... , ak ; 一开始有n堆硬币 , 每一堆有 xi 枚 。 A 和 B 轮流选出一堆硬币 ,从中取出硬币 , 每次所取硬币的枚数一定在a1 , a2 , a3 ,...., ak ; 里面,A先取,取光硬币的一方获胜 。
分析:这里引入一个概念 Grundy , 利用这个东西,很多游戏都可以转换为Nim>
只有一堆石头的情况
int grundy(int x){
集合S={};
for(j=1:k){
if(a_j<=x) 将grundy(x-a_j)加到S集合中
}
return 最小的不属于S的非负整数
}
rundy值:除(任意一步所能转移到 的状态 的Grundy值 )以外的最小非负整数,这样的Grundy值,和Nim中的一个石子堆类似,有如下性质:
mex{0,1,2}=3;mex{ 1, 2}=0 ; mex{ 2, 3}=1
1.Nim中有x颗石子的石子堆,能转移成有0,1,……,x-1堆石子的石子堆
2.从Grundy值为x的状态出发,可以转移到Grundy值为0,1,……,x-1的状态。
Nim:
所有石子堆的石子数xi的XOR
x1 XOR x2 XOR …XOR xk
为0必败,为1必胜
Grundy值等价于Nim中石子数,所以对于Grundy的情况:
所有硬币堆的Grundy的值
grundy(x1) XOR grundy(x2) XOR …… XOR grundy(xn)
为0必败,为1必胜。
#include <cstdio> #include <set> #include <algorithm> #define maxn 105 #define maxk 105 using namespace std; int N,K,X[maxn],A[maxk]; int grundy[maxn+1]; void solve(){ //轮到自己剩0的时候必败 grundy[0]=0; //计算grundy int max_x=*max_element(X,X+N); for(int j=1;j<=max_x;j++){ set<int> s; //存储当前的状态 for(int i=0;i<K;i++){ if(A[i]<=j) s.insert(grundy[j-A[i]]); } int g=0; //寻找当前状态的最小排斥 while(s.count(g)!=0) g++; grundy[j]=g; } int ans=0; for(int i=0;i<N;i++) ans^grundy[X[i]]; if(ans!=0) puts("Alice"); else puts("Bob"); } int main(){ scanf("%d%d",&N,&K); for(int i=0;i<N;i++){ scanf("%d",&A[i]); } for(int j=0;j<K;j++){ scanf("%d",&X[j]); } solve(); return 0; }