Nim Game是非常著名的游戏,它的变体也很多,在《编程之美》上也用了三章讲解它。
Wikipedia
上说的很详细,本文则是在对wiki上的内容阅读后的个人理解。 游戏概述: 有M堆石子,每堆石子的个数不一定相同,Bob和Alice交替取石子,只能在一堆石子上取,取的数目任意,但不能为0,也不能大于此堆石子的数目。获胜的判定有两种:一种是谁先把石子去完谁获胜,这种叫Normal Play;另一种是最最后把石子取完谁输,这种叫Misere Play。 假设有M堆石子,(H1,H2…HM),XOR(H1…HM) = X,我们的目标是将 (H1,H2…HM)变为(0,0…0),即XOR()=0,那么我们说任何XOR()=0的情况都是“安全状态”,对于Normal Play来说,取石子的那个人都想让自己取完石子后,石子堆为“安全状态”。 存在两个定理:
- 任何“不安全状态”都可以通过一次取石子变为“安全状态”
- 任何“安全状态”都通过一次取石子只能变为不安全状态
所以,在Normal Play情况下,初始状态很重要,如果初始状态为“不安全状态”,Bob获胜(先取者获胜),否则Alice获胜。
那在Misere Play情况下又怎么样那,先看一下两个特殊情况:
- 所有的堆的石子都是1,且有偶数个堆,那么Bob一定获胜
- 所有的堆的石子都是1,且有奇数个堆,那么Alice一定获胜
好的,任何取石子者都希望自己去完后,存在上面“2”提到的情况——所有的石子堆都不大于2,且有奇数个大小为1的堆。 那么先取者Bob怎样可以使自己获胜呢:
- 如果一开始就是上面“1”提到的状态,Bob必输
- 如果一开始就是上面“2”提到的状态,Bob必胜
- 不是“1”“2”状态,初始状态是“不安全状态”,Bob一开始完全可以按“Normal Play”的方式取石子,当发现可以使石子产生奇数个1时,则不按“Normal Play”的方式去取了,让其变为奇数个1。下面证明一下为什么这样Bob就会获胜:如果Alice想产生一个奇数个1的不安全状态,那Alice一定是从一个安全状态产生一个不安全状态的(因为Bob先取,他总会产生一个安全状态),那么产生奇数个1的安全状态就是“偶数个1”,此时Bob便会再取一个石子,直接产生一个奇数个1的不安全状态,不需要Alice产生
- 不是“1”“2”状态,初始状态是“安全状态”,Bob怎么取都会是一个“不安全状态”,Bob想让此状态为奇数个1,但可以达到此状态的安全状态是偶数个1,这是“1”提到的情况,矛盾。因此Bob不可能产生奇数个1,因此决定权在Alice手上了,Alice会获胜。
假设初始状态是不安全状态,Bob要怎样取石子才能产生一个安全状态呢?方法很简单。 我们假设XOR()=X,将X于每一堆石子的数量T做异或操作,如果结果小于T,则Bob将取此堆的石子,取得个数为T-T^X。
那么《编程之美》的扩展第一题也就解决了。
下面的代码实现了上面讲的内容:
public class NimClass {
private int XOR(int[] heaps){
int ret = 0;
for(int i=0;i<heaps.length;i++)
ret ^= heaps[i];
return ret;
}
private int max(int[] heaps){
int ret =0;
for(int i=0;i<heaps.length;i++)
if(heaps[i]>ret)
ret = heaps[i];
return ret;
}
/*
* If Pair.nb_remove is zero , it means that this taker will lose.
*/
public Pair nim( int[] heaps, boolean misere){
int X = XOR(heaps);
if(X==0){
/* The safe state */
if(max(heaps) > 1)
return new Pair (-1,0);
else{
for(int i=0;i<heaps.length;i++)
if(heaps[i]==1){
heaps[i]=0;
return new Pair (i,1);
}
return new Pair (-1,0);
}
}
else{
/* The unsafe state */
int chosen_heap=-1,nb_remove=-1;
for(int i=0;i<heaps.length;i++)
if((heaps[i]^X)<heaps[i]){
chosen_heap = i;
nb_remove = heaps[i]-(heaps[i]^X);
heaps[i] = heaps[i]^X;
break;
}
boolean hasTowMoreHeap = false;
int oneHeapNum = 0;
for(int i=0;i<heaps.length;i++)
if(i!=chosen_heap){
if(heaps[i]>1){
hasTowMoreHeap = true;
break;
}
else if (heaps[i]==1) oneHeapNum++;
}
if(misere){
if(hasTowMoreHeap)
return new Pair (chosen_heap,nb_remove);
else{
heaps[chosen_heap] = 1;
return new Pair (chosen_heap, nb_remove-1);
}
}
else{
return new Pair (chosen_heap, nb_remove);
}
}
}
class Pair{
int chosen_heap ,nb_remove ;
public Pair(int chosen_heap, int nb_remove){
this.chosen_heap = chosen_heap;
this.nb_remove = nb_remove;
}
}
}