题目描述
取球博弈
两个人玩取球的游戏。
一共有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
思路:
取球博弈这个问题思路比较明确就是将两个人所有可能取的情况进行遍历,最后看两个人手中球数量奇偶。遍历的话可采用深度优先搜索
不过直接深度优先搜索肯定不行,因为如果有1000个球那么每次有三种取法,一共差不多就有3的几百次方。这个数据简直是天文数字。
因此需要进行记忆化存储,将原先算过的数据进行保存。但是要保存所有数据也是不现实的,因为我们需要保存的结果是当剩下n个球,自己有m个球,别人有k个球时候的输赢。那么需要一个三维数组a[n][m][k],而n,m,k最大为1000,就需要1000M左右内存也不现实。
怎么办呢? 还是看题目要求,是要我们判断最后两个人球的奇偶,也就是说我们只要管奇偶性就可,也就是说当剩下300个球,自己3个球,别人8个球的情况和当剩下300个球,自己5个球,别人2个球这种情况最后输赢是一样的。因为自己球再多也是奇数,别人球再少也是偶数,而可选是球总数一样所以最后奇偶性也是一样的,输赢也一样。
首先我们把基础的深度优先搜索代码写出来,不要管记忆的问题。至少在小数据上是妥妥的过。下面是不加记忆的深度优先搜索。
public class SelectBall {
public static int[] a;
public static int min;
//返回当前剩下rest个球,自己有havenum个球,对方有othernum球
public static int f(int rest, int havenum, int othernum) {
//当剩下的球比最少可取的球数量还少意味着不能再取了,这样就可以直接计算结果(计算各自球的奇偶)
if (rest < min) {
if (havenum % 2 != 0 && othernum % 2 == 0)
return 2;
if (havenum % 2 != 0 && othernum % 2 != 0)
return 1;
if (havenum % 2 == 0 && othernum % 2 == 0)
return 1;
return 0;
}
boolean equalflag = false;//最差是否能平
for (int sel : a) {//遍历自己可以取的所有方式
if (sel > rest)
continue;
//rest-sel(自己取了剩下的) othernum别人还有的 havenum+sel(自己取了当前拥有的)
//这个语句是计算对方的胜负
int result = f(rest - sel, othernum, havenum + sel);
if (result == 0)//对方输了,自己必赢
return 2;
if (result == 1)//和对方平了,那么最差也能平
equalflag = true;
}
//是否平了,如果没平又不能赢就肯定输
if (equalflag)
return 1;
else
return 0;
}
public static void main(String[] args) {
int[] b = { 1, 2, 3, 4, 5 };//总共的球
a = new int[] { 1, 2, 3 };//一次可选的数目
min = 1;
for (int total : b)
System.out.println(f(total, 0, 0));
}
}
下面是进行记忆化保存的代码:
public class SelectBall {
public static int[] a;
public static int min;
//保存剩下i个球,自己有j个球(仅存奇偶,0偶,1奇),对方有k个球(仅存奇偶)胜负cash[i][j][k]
public static int[][][] cash = new int[1001][2][2];
public static int f(int rest, int havenum, int othernum) {
//剩下球不够取了可以直接分出胜负
if (rest < min) {
if (havenum % 2 != 0 && othernum % 2 == 0)//自己奇,对方偶(赢)
return 2;
if (havenum % 2 != 0 && othernum % 2 != 0)//自己奇,对方奇(平)
return 1;
if (havenum % 2 == 0 && othernum % 2 == 0)//自己偶,对方偶(平)
return 1;
return -1;//不能平不能赢剩下就是输
}
boolean equalflag = false;//标记是否最差可平
for (int sel : a) {//遍历这次自己可选的所有取法
if (sel > rest)
continue;
int result;
if (cash[rest - sel][othernum % 2][(havenum + sel) % 2] != 0)
//看缓存中有没有前面计算过的结果,有就不算了,没有就保存
result = cash[rest - sel][othernum % 2][(havenum + sel) % 2];
else {
result = f(rest - sel, othernum, havenum + sel);
cash[rest - sel][othernum % 2][(havenum + sel) % 2] = result;
}
if (result == -1)//对方输己方赢
return 2;
if (result == 1)//和对方最差能平
equalflag = true;
}
//若能平则平,否则既不能赢又不能平,那就输了
if (equalflag)
return 1;
else
return -1;
}
public static void main(String[] args) {
int[] b = { 1, 2, 3, 4, 5, 900 };//总的球数
a = new int[] { 1, 2, 3 };//一次可选球数目
min = 1;
for (int total : b) {//计算剩下b[]这些球,最后的输赢
char ch = 0;
switch (f(total, 0, 0)) {
case -1://输
ch = '-';
break;
case 1://平
ch = '0';
break;
case 2://赢
ch = '+';
break;
}
System.out.print(ch);
}
}
}