取球博弈
两个人玩取球的游戏。
一共有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
两个人玩取球的游戏。
一共有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
由于没接触过博弈,所以一开始自己把他当作一般的搜索做。。。自然而然结果是扯淡的。。。
它区别于一般搜索的原因是题目中一句话:双方都采用最聪明的取法
这就告诉我们,如果存在一种情况,我知道我必输,我就不可能按那种取法进行下去,我要尽可能往好了取,能赢就赢,不能赢也要平,所以不能直接搜穷举比赛结果来查是不是先抽者能赢,而是在搜索中体现出不同选择对于结果的影响,然后从中选择一种最有利于自身的选项。
因此,我用一个长度为3的数组来记录各种取法带来的结果,初始情况下值都是0,如果能赢则下标2的置1,能平下标1置1,会输则下标0置1.
这是每轮通过三个取法搜索带来的结果,那么如何选择最聪明的取法?
if(tmp[2]==1)return 0;
else if(tmp[1]==1) return 1;
else return 2;
在每轮搜索结束,返回给上一层的时候,如果tmp[2]==1,说明可以赢,因此选这个最佳方案,那么对于上一轮的人来说,他上一轮取法导致了这一轮玩家可以赢,而这个玩家又选最优解,因此上一轮的必输,返回0;第二个就是平局情况;第三个是无可奈何,毫无胜算,只能返回2让上一轮的人赢。
完整代码:
import java.util.*;
class Node{
int num,a,b,flag;
public Node(int num,int a,int b,int flag){
this.num=num;this.a=a;this.b=b;this.flag=flag;
}
}
public class Main {
static int [] p=new int[3];
static int dfs(Node node,int step){
int tmp[]={0,0,0};
if(node.num<p[0]){
// System.out.println(node.num+" "+node.a+" "+node.b);
if(node.flag==-1){
if(node.a%2!=0&&node.b%2==0) return 2;
else if(node.a%2==0&&node.b%2!=0) return 0;
else return 1;}
else {
if(node.a%2!=0&&node.b%2==0) return 0;
else if(node.a%2==0&&node.b%2!=0) return 2;
else return 1;
}
}
for(int i=0;i<3;i++){
if(node.num>=p[i]){
if(node.flag==1) {
tmp[dfs(new Node(node.num-p[i],node.a+p[i],node.b,-1),step+1)]=1;
}
else {
tmp[dfs(new Node(node.num-p[i],node.a,node.b+p[i],1),step+1)]=1;
}
}
}
// System.out.println("step:"+step+" 0:"+tmp[0]+" 1:"+tmp[1]+" 2:"+tmp[2]);
if(tmp[2]==1)return 0;
else if(tmp[1]==1) return 1;
else return 2;
}
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
for(int i=0;i<3;i++){
p[i]=sc.nextInt();
}
String s="";
for(int i=0;i<5;i++){
int num=sc.nextInt(),res;
res=dfs(new Node(num,0,0,1),0);
if(res==0) s+="+ ";
else if(res==1) s+="0 ";
else s+="- ";
// System.out.println("***********");
}
System.out.println(s);
}
}
由于我用Node来记录每次的情况,为了知道这轮谁取,就通过flag的值来判断,这也导致,在无法取球后,判断胜负的条件也是不一样的,所以要写两个不同情况的反馈。
也因为我用Node,我无法记录曾经出现过的情况,这回导致搜索时间变长,我会再修改。
贴一段学长的代码,用数组来记录的,但其中有些我还没看懂,还要再问。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int a, b, c;
int buf[1000][2][2];
int dfs(int n, int u, int v) {
if (n < a && n < b && n < c) {
if (u==v)
return 1;
else if (u)
return 0;
else
return 2;
}
if (~buf[n][u][v])
return buf[n][u][v];
int f[3];
f[0] = f[1] = f[2] = 0;
if (n >= a)
f[dfs(n-a, v, (u+a)&1)]++;
if (n >= b)
f[dfs(n-b, v, (u+b)&1)]++;
if (n >= c)
f[dfs(n-c, v, (u+c)&1)]++;
if (f[2])
buf[n][u][v] = 0;
else if (f[1])
buf[n][u][v] = 1;
else
buf[n][u][v] = 2;
return buf[n][u][v];
}
int main(int argc, char const *argv[])
{
scanf("%d%d%d", &a, &b, &c);
memset(buf, -1, sizeof(buf));
for (int i = 0; i < 5; i++) {
int n;
scanf("%d", &n);
int ans = dfs(n, 0, 0);
if (ans == 0)
putchar('+');
else if (ans == 1)
putchar('0');
else
putchar('-');
putchar(i==4?'\n':' ');
}
return 0;
}