移豆子游戏是这样玩的,有一定数目的豆子(比方说有100粒),然后规定,两个人依次拿走一些数量的豆子数,每次拿走的数目有上限(比方说3粒),而且至少拿一粒。最后,谁拿走最后一堆豆子算谁输(当然,也可以设定最后拿走的赢)。
这其实是个数学趣题,高中的时候还搞定不了,不过现在自己想想倒也轻松搞定,看来的确是有进步啊。
假定游戏的完法是谁拿走最后一堆豆子算谁输(会分析这个,谁拿走最后一堆豆子算谁赢的必胜玩法就会容易搞定),我来分析一下这个游戏的奥秘,QUIT EASY!FOLLOW ME:
先分析个别情况,(然后推广至一般情况)假定每次可拿子数为3粒,谁拿走最后一堆豆子算谁输。
先手必胜用0表示,而先手必败用1表示(待会儿你就明白为什么要这样干了)
ATTENTION:
我们只讨先手必胜或必败的情况,因为当先手必胜时,后手必败;反之亦然
每次最多拿三粒。
1粒:你是先手吗?噢,很不幸,你必败了。 1
2粒:你是先手吗?呵呵,拿一粒,对手面对一粒,你必胜,而他必败。0
3粒:有了前车之鉴,先手的你应该聪明了,对了,拿走两粒,你必 0
胜。
4粒:看准了您呢,三粒,对手必败。 0
5粒:唉呀呀,惨了,拿1,2,3粒时,对手得的子数为4,3,2,对 1
手都必胜了,你必败了。
6粒:只要拿一粒,让对手只拿到5粒,对手就必败了。 0
7粒:2粒,理由同前。 0
8粒:3粒,理由同前。 0
9粒:完了,对手拿到6,7,8粒时对手都必胜,你必败了。 1
10粒:1粒,让对手拿10粒必败。 0
11粒:看出规律了吗,对了,取2粒。 0
。。。
综上,归纳一下这个小游戏的必胜算法:
谁拿走最后一堆谁输的情况:当先手遇到的子数为1+k*(n+1)时(其中k为整数,n为每次可拿的最大子数),先手是必败的,其余情况时先手必胜,所以,这时游戏的双方只有一个目的,把给对手的子凑到1+k*(n+1)就Ok了。
再进一步推一下,对手必胜,对你而言,你就是必败,返之亦然。
1:1
2:对于1的结果取反,为0。
3:对上面两个结果取反,再取交集。(1&&0)=0
4:对上面三个结果取反,再取交集。(0&&1&&1)=0
5:对上面三个结果取反,再取交集。(1&&1&&1)=1必败。
。。。GOT IT?
谁拿走最后一堆谁赢的情况:分析的方法和上面是一样的(略),小结为先手遇到子数为k*(n+1)时必败。
下面为实现这个效果的一个AI类(C++语言描述)
class AI{
public:
int HighAI(int winMode,int netNum,int limitNum);
int LowAI(int netNum,int limitNum);
int AIThinking(int winMode,int netNum,int limitNum,int aiLevel);
};
int AI::HighAI(int winMode,int netNum,int limitNum){
int outNum=0;
int t=0;
switch(winMode){//1谁拿走最后的最输,0谁拿走最后的谁赢
case 1:
if((t=(netNum-1)%(limitNum+1))==0){//无奈了,这可是必败的局面,只好等对手出错了。
outNum=LowAI(netNum,limitNum);
}
else{//一击必杀
outNum=t;
}
break;
case 0:
if((t=(netNum%(limitNum+1)))==0){//无奈了,这可是必败的局面,只好等对手出错了。
outNum=LowAI(netNum,limitNum);
}
else{
outNum=t;
}
break;
}
return outNum;
}
int AI::LowAI(int netNum,int limitNum){
int outNum=0;
randomize();
do{
outNum=random(limitNum)+1;
}while(outNum>netNum);
return outNum;
}
int AI::AIThinking(int winMode,int netNum,int limitNum,int aiLevel){
int outNum=0;
switch(aiLevel){//富有可调性质的AILEVEL以后再做
case 0:outNum=LowAI(netNum,limitNum);break;
case 1:outNum=HighAI( winMode,netNum,limitNum);break;
}
return outNum;
}