编程之美-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;
	}
}

你可能感兴趣的:(编程之美)