编程之美-NIM游戏分析-石头总数为奇数时如何保证先动手者必胜




import java.util.Arrays;
import java.util.Random;

public class Nim {

/**编程之美 NIM游戏分析
问题:
有N块石头和两个玩家A和B,玩家A先将石头随机分成若干堆,然后按照BABA...的顺序不断轮流取石头,
能将剩下的石头一次取光的玩家获胜,每次取石头时,每个玩家只能从若干堆石头中任选一堆,
取这一堆石头中任意数目(大于0)个石头。
请问:
玩家A要怎样分配和取石头才能保证自己有把握取胜?

1.如果石头的个数N为偶数,A只要将其分为相同的两份,就一定能取胜。
初始:XOR(M1, M1) == 0
玩家B:XOR(M1, M2) != 0 (其中一堆的个数减少到M2)
玩家A:XOR(M2, M2) == 0 (玩家A将另一堆的个数也减少到M2)
结果:XOR(M2, M2) == 0 (直到结束状态(0, 0))

2.如果石头的个数N为奇数,B有必胜的方法。
初始:XOR(M1, M2, ... , Mn) != 0
玩家B:XOR(M1, ... , Mi', ... , Mn) == 0 (其中一堆Mi的个数减少到Mi')
玩家A:XOR(M1, ... , Mj', ... , Mn) != 0
玩家B:XOR(M1, ... , Mi', ... , Mn) == 0 (其中一堆Mi的个数减少到Mi')
结果:XOR(M1, ... , Mj' , ... , Mn) == 0 (直到结束状态(0,0))

这里就有个问题:已知XOR(M1, M2, ... , Mn) != 0,玩家B该改变那个Mi以使得XOR(M1, ... , Mi', ... , Mn) == 0呢

刚开始我的思路是基于以下结论:
1.Mi改变成Mi'后,如果数组可以分成和相等的两部分,那么数组所有元素异或的结果一定是0
--这个结论是错的,例如2^7^9=12,尽管9^(2+7)=0
2.Mi=max(M1,M2...Mn)
--这也是错的。从max里面取走x块石头与非max里面取走x块石头,结果是不一样的
--例如(9,7,9),取走7块石头,有两种情况:XOR(9,0,9)=0; XOR(2,7,9)=12

正确的思路:
令xor=XOR(M1,M2,...Mi-1,Mi,Mi+1,...Mn);
Mi'=Mi^xor=XOR(M1,M2,...Mi-1,Mi+1,...Mn)
那么XOR(M1,M2,...Mi-1,Mi',Mi+1,...Mn)=0
*/
public static void main(String[] args) {
int numOfHeap=5; //石头堆数
for(int i=0;i<10;i++){ //测试10次
int[] stones=generateStones(numOfHeap);
process(stones);
System.out.println("=================================");
}

}

//当前石头总数为奇数时,如何取石头才能使自己必胜(即使异或结果为0)
public static void process(int[] a){

if(a==null||a.length<2){
return;
}

int size=a.length;
int xor=0;
for(int i=0;i<size;i++){
xor ^=a[i];
}

if(xor==0){ //数组和(石头总数)为奇数,则不管怎么分堆,分堆的异或结果一定不是0。其他情况不在本次讨论范围内
return;
}

int i=0;
int diff=0;
int mi=0;
for(;i<size;i++){
mi=a[i]^xor;
if(a[i]>=mi){
diff=a[i]-mi;
break;
}
}

System.out.println(Arrays.toString(a));
System.out.println("now you should take "+diff+" stones from a["+i+"]="+a[i]);

//验证一下现在异或结果是不是0
a[i]=mi; //取走石头
xor=0;
for(int x:a){
xor ^=x;
}
System.out.println("XOR"+Arrays.toString(a)+"="+xor);

}

private static Random random=new Random();

//产生指定堆数的石头数组,且保证石头总数为奇数
public static int[] generateStones(int numOfHeap){
if(numOfHeap<2){
return new int[0];
}
int[] stones=new int[numOfHeap];
int sum=0;
for(int i=0;i<numOfHeap;i++){
stones[i]=random.nextInt(10)+1;
sum +=stones[i];
}
if(sum%2==0){ //保证石头总数为奇数
stones[0] +=1;
}
return stones;
}
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值