取球博弈问题(第七届蓝桥杯个人省赛JavaB组真题)

题目描述:

取球博弈
两个人玩取球的游戏。
一共有N个球,每人轮流取球,每次可取集合{n1,n2,n3}中的任何一个数目。
如果无法继续取球,则游戏结束。
此时,持有奇数个球的一方获胜。
如果两人都是奇数,则为平局。
假设双方都采用最聪明的取法,
第一个取球的人一定能赢吗?
试编程解决这个问题。
输入格式:
第一行3个正整数n1 n2 n3,空格分开,表示每次可取的数目 (0<n1,n2,n3<100)
第二行5个正整数x1 x2 … x5,空格分开,表示5局的初始球数(0<xi<1000)
输出格式:
一行5个字符,空格分开。分别表示每局先取球的人能否获胜。
能获胜则输出+,
次之,如有办法逼平对手,输出0,
无论如何都会输,则输出-
例如,输入:
1 2 3
1 2 3 4 5
程序应该输出:
+ 0 + 0 -
再例如,输入:
1 4 5
10 11 12 13 15
程序应该输出:
0 - 0 + +
再例如,输入:
2 3 5
7 8 9 10 11
程序应该输出:
+ 0 0 0 0
资源约定:
峰值内存消耗(含虚拟机) < 256M
CPU消耗 < 3000ms

思路:

我们先来理解一下题意:

表示每局先取球的人能否获胜。
能获胜则输出+,
次之,如有办法逼平对手,输出0,
无论如何都会输,则输出-

看到这里,我们基本的思路是:
       采用深度遍历搜索(dfs)遍历所有不同的走法,得出每种走法会出现的结果。
       只要在遍历过程中发现有先取球的人获胜的结果出现,那就不用再继续遍历下去了,直接return +
       若程序遍历完所有的走法,说明所有走法都没有出现先取球的人获胜的结果,那么,在我们遍历完的所有走法中,只要有出现平局的情况,则返回0,没有平局的情况则返回   -
       但是
       若按照以上思路去解我们就漏掉了题意当中很关键的一个点:

假设双方都采用最聪明的取法

       举一个具体例子来理解:
       一局游戏,开局总球数为N = 5,每次可取的数目可以是n1 = 1;
n2 = 2; n3 = 3; 玩家A B;设A为先取球的人。
双方都采用最聪明的走法:

走法第一次取球第二次取球第三次取球结果
走法一A取1颗球B取3颗球A只能取1颗球B胜
走法二A取2颗球B取3颗球B胜
走法三A取3颗球B取1颗球A只能取1颗球B胜

以上,为双方均采用最聪明的走法时所出现的所有可能情况。
结果表明A无法胜利,该局游戏应该return -

如果按照我们一开始的基本思路来设计程序,程序会遍历到一种情况

第一次取球第二次取球第三次取球第四次取球第五次取球结果
A取1颗球B取1颗球A取1颗球B取1颗球A取1颗球A胜

此时程序就会输出 + 显然不符合题意,得到了错误的答案。

因此, 我们采用深度遍历搜索(dfs)遍历所有不同的走法时,还要避免 “不聪明” 的走法带来的错误结果。

除此之外,

直接深度优先搜索肯定不行,因为如果有1000个球那么每次有三种取法,一共差不多就有3的几百次方。这个数据简直是天文数字。
因此需要进行记忆化存储,将原先算过的数据进行保存。但是要保存所有数据也是不现实的,因为我们需要保存的结果是当剩下n个球,自己有m个球,别人有k个球时候的输赢。那么需要一个三维数组a[n][m][k],而n,m,k最大为1000,就需要1000M左右内存也不现实。
怎么办呢? 还是看题目要求,是要我们判断最后两个人球的奇偶,也就是说我们只要管奇偶性就可,也就是说当剩下300个球,自己3个球,别人8个球的情况和当剩下300个球,自己5个球,别人2个球这种情况最后输赢是一样的。因为自己球再多也是奇数,别人球再少也是偶数,而可选是球总数一样所以最后奇偶性也是一样的,输赢也一样。

代码实现:

无记忆深度优先搜索代码实现

首先我们把基础的深度优先搜索代码写出来,不要管记忆的问题。至少在小数据上是妥妥的过。下面是不加记忆的深度优先搜索。


    public static void main(String[] args){twentyFour(); }

    /**
     * 取球博弈问题
     */
    public static void twentyFour(){
        Scanner scanner = new Scanner(System.in);
        String nStr = scanner.nextLine();
        String xStr = scanner.nextLine();
        //n1 n2 n3储存在该数组中(每次可取球数的集合)
        int[] nArray = new int[3];
        //x1 x2 x3 x4 x5储存在该数组中(5局的初始球数)
        int[] xArray = new int[5];
        //每次可取的最小球数
        int min = 99;
        //将输入的值初始化以上两个数组
        String[] split;
        split = nStr.split(" ");
        for (int i = 0;i < 3;i++){
            nArray[i] = Integer.valueOf(split[i]);
        }
        split = xStr.split(" ");
        for (int i = 0;i < 5;i++){
            xArray[i] = Integer.valueOf(split[i]);
        }
        //找到每次可取的最小球数
        for (int i : nArray){
            if (i < min){
                min = i;
            }
        }
        StringBuffer stringBuffer = new StringBuffer();
        //五局比赛 五次循环
        for (int i = 0;i < 5;i++){
            char result = dfs(0,0,0,xArray[i],nArray,min);
            stringBuffer.append(result + " ");
        }
        System.out.println(stringBuffer.toString());
    }

    /**
     *
     * @param player 本轮次取球的玩家  0表示本局游戏第一个取球的玩家A   1表示另一名玩家B
     * @param ABallNum  A玩家手上拥有的球数
     * @param BBallNum  B玩家手上拥有的球数
     * @param totalBallNum  目前剩余的可取球数
     * @param eachGetBallNum 本轮该玩家的取球数目
     * @param min 每次可取的最小球数,用于判断游戏结束
     * @return
     */
    public static char dfs(int player, int ABallNum, int BBallNum, int totalBallNum,
                           int[] eachGetBallNum, int min){

        //判定胜负
        if (min > totalBallNum){
            if (ABallNum%2 == 0 && BBallNum%2 == 1){
                return  '-';
            } else if (ABallNum%2 == 1 && BBallNum%2 == 0){
                return '+';
            } else {
                return '0';
            }
        }
        char result = 'a';
        char temp = 'a';
        if (player == 0){
            for (int i = 0;i < 3;i++){
                if (totalBallNum-eachGetBallNum[i] >= 0){
                    //只有目前剩余的可取球数足够本轮该玩家要取球的数目才能走这一步
                    temp = dfs(1,ABallNum+eachGetBallNum[i],BBallNum,
                            totalBallNum-eachGetBallNum[i],eachGetBallNum,min);
                }
                //对于第一个取球的人来说,能让自己赢的走法就是最“聪明”的走法,因此‘+’的优先级最高,
                //‘0’的优先级次之,‘-’的优先级最低
                if (temp == '+'){
                    return temp;
                } else if (temp == '0'){
                    result = '0';
                } else {
                    if (result != '0'){
                        result = '-';
                    }
                }
            }
        } else if (player == 1){
            for (int i = 0;i < 3;i++){
                if (totalBallNum-eachGetBallNum[i] >= 0){
                    //只有目前剩余的可取球数足够本轮该玩家要取球的数目才能走这一步
                    temp = dfs(0,ABallNum,BBallNum+eachGetBallNum[i],
                            totalBallNum-eachGetBallNum[i],eachGetBallNum,min);
                }
                //对于第二个取球的人来说,能让对方输的走法就是最“聪明”的走法,因此‘-’的优先级最高,
                //‘0’的优先级次之,‘+’的优先级最低
                if (temp == '-'){
                    return temp;
                } else if (temp == '0'){
                    result = '0';
                } else {
                    if (result != '0'){
                        result = '+';
                    }
                }
            }
        }
        return result;
    }


加上记忆化保存的代码实现


public static void main(String[] args){twentyFour(); }


/**
 * 取球博弈问题
 */
public static void twentyFour(){
    Scanner scanner = new Scanner(System.in);
    String nStr = scanner.nextLine();
    String xStr = scanner.nextLine();
    //n1 n2 n3储存在该数组中(每次可取球数的集合)
    int[] nArray = new int[3];
    //x1 x2 x3 x4 x5储存在该数组中(5局的初始球数)
    int[] xArray = new int[5];
    //每次可取的最小球数
    int min = 99;
    //将输入的值初始化以上两个数组
    String[] split;
    split = nStr.split(" ");
    for (int i = 0;i < 3;i++){
        nArray[i] = Integer.valueOf(split[i]);
    }
    split = xStr.split(" ");
    for (int i = 0;i < 5;i++){
        xArray[i] = Integer.valueOf(split[i]);
    }
    //找到每次可取的最小球数
    for (int i : nArray){
        if (i < min){
            min = i;
        }
    }
    StringBuffer stringBuffer = new StringBuffer();
    //保存剩下i个球,自己有j个球(仅存奇偶,0偶,1奇),对方有k个球(仅存奇偶,0偶,1奇)轮到L玩家取球(0第一个取球的玩家,1对手)
    //结果dp[i][j][k][L]
    //本数组具有通用性,因此不需要每局游戏都重新初始化本数组
    char[][][][] dp = new char[1001][2][2][2];
    //五局比赛 五次循环
    for (int i = 0;i < 5;i++){
        char result = dfs(dp,0,0,0,xArray[i],nArray,min);
        stringBuffer.append(result + " ");
    }
    System.out.println(stringBuffer.toString());
}


/**
 * @param dp  保存剩下i个球,第一个取球的人有j个球(仅存奇偶,0偶,1奇),对方有k个球(仅存奇偶,0偶,1奇) 结果dp[i][j][k]
 * @param player 本轮次取球的玩家  0表示本局游戏第一个取球的玩家A   1表示另一名玩家B
 * @param ABallNum  A玩家手上拥有的球数
 * @param BBallNum  B玩家手上拥有的球数
 * @param totalBallNum  目前剩余的可取球数
 * @param eachGetBallNum 本轮该玩家的取球数目
 * @param min 每次可取的最小球数,用于判断游戏结束
 * @return
 */
public static char dfs(char[][][][] dp,int player, int ABallNum, int BBallNum, int totalBallNum,
                       int[] eachGetBallNum, int min){

    //判定胜负
    if (min > totalBallNum){
        if (ABallNum%2 == 0 && BBallNum%2 == 1){
            return  '-';
        } else if (ABallNum%2 == 1 && BBallNum%2 == 0){
            return '+';
        } else {
            return '0';
        }
    }
    char result = 'a';
    char temp = 'a';
    //根据本轮次取球的玩家进行分情况
    if (player == 0){
        for (int i = 0;i < 3;i++){
            if (totalBallNum-eachGetBallNum[i] >= 0){
                //只有目前剩余的可取球数足够本轮该玩家要取球的数目才能走这一步
                //根据第一次取球的玩家及对手拥有球数的求偶分四种情况
                if ((ABallNum+eachGetBallNum[i])%2 == 0 && BBallNum%2 == 0){
                    if (dp[totalBallNum-eachGetBallNum[i]][0][0][1] != '\u0000'){
                        //java中char类型如果没有初始化就是\u0000 即一个空格
                        //如果这种情况已经计算过,直接返回就ok
                        temp = dp[totalBallNum-eachGetBallNum[i]][0][0][1];
                    } else {
                        dp[totalBallNum-eachGetBallNum[i]][0][0][1] = dfs(dp,1,ABallNum+eachGetBallNum[i],BBallNum,
                                totalBallNum-eachGetBallNum[i],eachGetBallNum,min);
                        temp = dp[totalBallNum-eachGetBallNum[i]][0][0][1];
                    }
                } else if ((ABallNum+eachGetBallNum[i])%2 == 1 && BBallNum%2 == 0){
                    if (dp[totalBallNum-eachGetBallNum[i]][1][0][1] != '\u0000'){
                        //java中char类型如果没有初始化就是\u0000 即一个空格
                        //如果这种情况已经计算过,直接返回就ok
                        temp = dp[totalBallNum-eachGetBallNum[i]][1][0][1];
                    } else {
                        dp[totalBallNum-eachGetBallNum[i]][1][0][1] = dfs(dp,1,ABallNum+eachGetBallNum[i],BBallNum,
                                totalBallNum-eachGetBallNum[i],eachGetBallNum,min);
                        temp = dp[totalBallNum-eachGetBallNum[i]][1][0][1];
                    }
                } else if ((ABallNum+eachGetBallNum[i])%2 == 0 && BBallNum%2 == 1){
                    if (dp[totalBallNum-eachGetBallNum[i]][0][1][1] != '\u0000'){
                        //java中char类型如果没有初始化就是\u0000 即一个空格
                        //如果这种情况已经计算过,直接返回就ok
                        temp = dp[totalBallNum-eachGetBallNum[i]][0][1][1];
                    } else {
                        dp[totalBallNum-eachGetBallNum[i]][0][1][1] = dfs(dp,1,ABallNum+eachGetBallNum[i],BBallNum,
                                totalBallNum-eachGetBallNum[i],eachGetBallNum,min);
                        temp = dp[totalBallNum-eachGetBallNum[i]][0][1][1];
                    }
                } else if ((ABallNum+eachGetBallNum[i])%2 == 1 && BBallNum%2 == 1){
                    if (dp[totalBallNum-eachGetBallNum[i]][1][1][1] != '\u0000'){
                        //java中char类型如果没有初始化就是\u0000 即一个空格
                        //如果这种情况已经计算过,直接返回就ok
                        temp = dp[totalBallNum-eachGetBallNum[i]][1][1][1];
                    } else {
                        dp[totalBallNum-eachGetBallNum[i]][1][1][1] = dfs(dp,1,ABallNum+eachGetBallNum[i],BBallNum,
                                totalBallNum-eachGetBallNum[i],eachGetBallNum,min);
                        temp = dp[totalBallNum-eachGetBallNum[i]][1][1][1];
                    }
                }
            }
            //对于第一个取球的人来说,能让自己赢的走法就是最“聪明”的走法,因此‘+’的优先级最高,
            //‘0’的优先级次之,‘-’的优先级最低
            if (temp == '+'){
                return temp;
            } else if (temp == '0'){
                result = '0';
            } else {
                if (result != '0'){
                    result = '-';
                }
            }
        }
    } else if (player == 1){
        for (int i = 0;i < 3;i++){
            if (totalBallNum-eachGetBallNum[i] >= 0){
                //只有目前剩余的可取球数足够本轮该玩家要取球的数目才能走这一步
                //根据第一次取球的玩家及对手拥有球数的求偶分四种情况
                if (ABallNum%2 == 0 && (BBallNum+eachGetBallNum[i])%2 == 0){
                    if (dp[totalBallNum-eachGetBallNum[i]][0][0][0] != '\u0000'){
                        //java中char类型如果没有初始化就是\u0000 即一个空格
                        //如果这种情况已经计算过,直接返回就ok
                        temp = dp[totalBallNum-eachGetBallNum[i]][0][0][0];
                    } else {
                        dp[totalBallNum-eachGetBallNum[i]][0][0][0] = dfs(dp,0,ABallNum,BBallNum+eachGetBallNum[i],
                                totalBallNum-eachGetBallNum[i],eachGetBallNum,min);
                        temp = dp[totalBallNum-eachGetBallNum[i]][0][0][0];
                    }
                } else if (ABallNum%2 == 1 && (BBallNum+eachGetBallNum[i])%2 == 0){
                    if (dp[totalBallNum-eachGetBallNum[i]][1][0][0] != '\u0000'){
                        //java中char类型如果没有初始化就是\u0000 即一个空格
                        //如果这种情况已经计算过,直接返回就ok
                        temp = dp[totalBallNum-eachGetBallNum[i]][1][0][0];
                    } else {
                        dp[totalBallNum-eachGetBallNum[i]][1][0][0] = dfs(dp,0,ABallNum,BBallNum+eachGetBallNum[i],
                                totalBallNum-eachGetBallNum[i],eachGetBallNum,min);
                        temp = dp[totalBallNum-eachGetBallNum[i]][1][0][0];
                    }
                } else if (ABallNum%2 == 0 && (BBallNum+eachGetBallNum[i])%2 == 1){
                    if (dp[totalBallNum-eachGetBallNum[i]][0][1][0] != '\u0000'){
                        //java中char类型如果没有初始化就是\u0000 即一个空格
                        //如果这种情况已经计算过,直接返回就ok
                        temp = dp[totalBallNum-eachGetBallNum[i]][0][1][0];
                    } else {
                        dp[totalBallNum-eachGetBallNum[i]][0][1][0] = dfs(dp,0,ABallNum,BBallNum+eachGetBallNum[i],
                                totalBallNum-eachGetBallNum[i],eachGetBallNum,min);
                        temp = dp[totalBallNum-eachGetBallNum[i]][0][1][0];
                    }
                } else if (ABallNum%2 == 1 && (BBallNum+eachGetBallNum[i])%2 == 1){
                    if (dp[totalBallNum-eachGetBallNum[i]][1][1][0] != '\u0000'){
                        //java中char类型如果没有初始化就是\u0000 即一个空格
                        //如果这种情况已经计算过,直接返回就ok
                        temp = dp[totalBallNum-eachGetBallNum[i]][1][1][0];
                    } else {
                        dp[totalBallNum-eachGetBallNum[i]][1][1][0] = dfs(dp,0,ABallNum,BBallNum+eachGetBallNum[i],
                                totalBallNum-eachGetBallNum[i],eachGetBallNum,min);
                        temp = dp[totalBallNum-eachGetBallNum[i]][1][1][0];
                    }
                }
            }
            //对于第二个取球的人来说,能让对方输的走法就是最“聪明”的走法,因此‘-’的优先级最高,
            //‘0’的优先级次之,‘+’的优先级最低
            if (temp == '-'){
                return temp;
            } else if (temp == '0'){
                result = '0';
            } else {
                if (result != '0'){
                    result = '+';
                }
            }
        }
    }
    return result;
}

希望对大家有所帮助哦~~

参考:取球博弈问题 蓝桥杯

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值