奥林匹克竞赛题-判断先手必赢的问题

问题描述: 有 三个 盒子,每个盒子内有若干个球,两个人轮流取,每人每次只能从一个盒子内取至少一个球,取到最后一个球的人输。

   比如3,4,5的话,先手通过特定的步骤能够获胜。


问题分析:没有找到必胜的潜在规律,因此,编写遍历所有情况,以判断先手是否必胜。这是一个递归调用的问题,因此需要设置递归的结束条件。

通过树汇总所有的情况,然后将可以胜利的每个步骤都打印出来。


具体的代码如下:

package game;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;


public class howWin {
    private int BOX_NUM = 0;
    private int[] BoxList = null;
    private treeNode travelTree;
    private List<treeNode> YesNode = new ArrayList<treeNode>();
    private List<treeNode> otherFixedFailState = new ArrayList<treeNode>();
    public class treeNode {

        public List<treeNode> childs;
        public String nodeInfo;
        public treeNode father;
        public treeNode(String info){
            nodeInfo = info;
            childs = new ArrayList();
            childs.clear();
        }
        public void addNode(treeNode tree) {
            childs.add(tree);
            tree.father = this;
        }

    }
    public howWin(){
    }

    public void setBoxBallNum(int[] boxSum){
        BoxList = boxSum;
        BOX_NUM = BoxList.length;
        travelTree = new treeNode("Root" + genListInfo(BoxList));
    }

    // 必然可以获胜的情形
    public boolean fixWin(int[] middleState){
        int countNoEmptyBox = 0;
        List<Integer> noEmptyIndex = new ArrayList<Integer>();
        for (int i = 0; i < BOX_NUM; i++){
            if (middleState[i] > 0) {
                countNoEmptyBox += 1;
                noEmptyIndex.add(i);
            }
        }
        // 只剩一个盒子内有球,并且球的数量不少于2        if (countNoEmptyBox == 1 && middleState[noEmptyIndex.get(0).intValue()] >= 2)
            return true;
        // 两个盒子中有球,并且一个盒子中球的数量是1
        if (countNoEmptyBox == 2
                && (middleState[noEmptyIndex.get(0).intValue()] == 1
                    || middleState[noEmptyIndex.get(1).intValue()] == 1)){
            return true;
        }
        return false;
    }

    public boolean afterOtherActionCanWin(int[] middleState, treeNode travel){
        if (fixWin(middleState)) {
            treeNode cur = new treeNode("NO");
            travel.addNode(cur);
            return false;
        }
        for (int i = 0; i < BOX_NUM; i++){
            for (int j = 1; j <= middleState[i]; j++){
                int[] tmp = middleState.clone();
                tmp[i] -= j;
                treeNode cur = new treeNode("B|"+genListInfo(tmp));
                travel.addNode(cur);
                if (!canWin(tmp, cur)){
                    return false;
                }
            }
        }
        return true;
    }

    public boolean canWin(int[] middleState, treeNode travel){
        if (fixWin(middleState)) {
            treeNode cur = new treeNode("YES");
            travel.addNode(cur);
            YesNode.add(cur);
            return true;
        }
        for (int i = 0; i < BOX_NUM; i++){
            for (int j = 1; j <= middleState[i]; j++){
                int[] tmp = middleState.clone();
                tmp[i] -= j;
                treeNode cur = new treeNode("A|" + genListInfo(tmp));
                travel.addNode(cur);
                if (afterOtherActionCanWin(tmp, cur)){
                    otherFixedFailState.add(cur);
                    return true;
                }
            }
        }
        return false;
    }
    private  String genListInfo(int[] midState){
        String Info = "";
        for (int i = 0; i < midState.length; i++)
            Info += midState[i] + " ";
        return Info;
    }

    public boolean caculateResult(){
        if (canWin(BoxList, travelTree))
        {
            System.out.println("YES: the first one sure win");
            return true;
        }
        System.out.println("NOT SURE: the first win");
        return false;
    }

    public void dept(treeNode cur, List<treeNode> mustShow, int depts){
        if (depts % 2 == 1 && !mustShow.contains(cur)){
            return;
        }

        System.out.print("["+depts+"]");
        for (int i = 0; i < depts; i++) {
            System.out.print("----");
        }
        System.out.println(cur.nodeInfo);
        for (int i = 0; i < cur.childs.size(); i++) {
            dept(cur.childs.get(i), mustShow, depts+1);
        }
    }
    public void printWinSteps(){
        List<treeNode> mustShowNodes = new ArrayList<>();
        for (int i = 0; i < YesNode.size(); i++)
        {
            treeNode cur = YesNode.get(i);
            while (cur != null){
                mustShowNodes.add(cur);
                cur = cur.father;
            }
        }
        dept(travelTree, otherFixedFailState, 0);
    }

    public static int[] scanBoxBallInfo(){
        Scanner sb = new Scanner(System.in);
        System.out.print("退出请输入q. \n 输入每个盒子内的小球数目,\n如三个盒子,每个盒子放345个球,则输入为:3,4,5\n");
        String name = sb.nextLine();
        if (name.contains("q") || name.contains("Q"))
        {
            System.exit(0);
        }
        String[] ballSum = name.split(",");
        int[] ballSumInt = new int[ballSum.length];
        boolean parseOk = true;
        for (int i = 0; i < ballSum.length; i++){
            try {
                ballSumInt[i] = Integer.parseInt(ballSum[i]);
            }catch (Exception e){
                e.printStackTrace();
                parseOk = false;
            }
            if (ballSumInt[i] < 0) {
                System.out.println("输入有异常,请重新输入");
                parseOk = false;
                break;
            }
        }
        if (!parseOk)
            return null;
        return ballSumInt;
    }

    public static void main(String[] args){

        while(true) {
            int[] ballSum = scanBoxBallInfo();
            if (ballSum == null)
                continue;

            howWin cur = new howWin();
            cur.setBoxBallNum(ballSum);
            if (cur.caculateResult())
                cur.printWinSteps();
        }
    }
}

代码分析:

1. 此代码实现了基本的遍历内容,但是有很大的优化空间,比如判断先手的胜利的步骤,可以汇总从拿取最多球的开始遍历,找到正确步骤的速度会快很多。

2. 该代码打印先手胜利的一种方法,但是不是唯一方法。


请大家批评指正!




猜你喜欢

转载自blog.csdn.net/u013516079/article/details/80049340