硬币游戏 (博弈入门)

题目:

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;
} 
 

 

posted @ 2018-08-29 11:21  shuai_hui  阅读(1383)  评论(0编辑  收藏  举报