以前虽然也研究过博弈论,但其实还是一知半解,更多的是记住现有结论,生硬地套用而已,今天重新看了一遍巴什博奕,有了不一样的体会,终于明白了其中的原理。
有一堆物品,共n个,两人轮流从这堆物品中取,规定每次至少取一个,至多m个,获胜条件分为两种,分别是最后取光者胜或者是最后取光者败。
若n=m+1,则无论先手取走多少,后手一定可以一次性取走剩下的全部物品,这时后手胜;
若n=(m+1)*r+s,且s<=m,r为任意自然数,此时先手只要先取s个,那么后手取k个(k<=m),然后先手再取m+1-k个,也就是某一组m+1个中的剩下的物品,这时候总数成为(m+1)*(r-1),所以局面成为(m+1)的任意倍,那么在接下来的游戏过程中,无论后手取多少,先手总可以取走对应的数目从而使得总数恢复到(m+1)的整数倍,一直这样下去,先手必胜。
因此,我们总结一下,在这样的游戏规则下,求k=n%(m+1),若k不为0,则此时相当于k就是上文中提到的s,所以先手必胜,若k为0呢,则先手面临的就是当前总数为(m+1)的整数倍的情形,所以必败。
可能我们会想:为什么一定是m+1的整数倍呢?就因为我们是从n=m+1开始分析的吗?当然不是这样的,因为以(m+1)的整数倍作为判断标准可以穷尽全部情况,假设我们修改一下上文中的公式,改成:n=(m+2)*r+s(s<=m),我们可以提取出一个r来,变成:n=(m+1)*r+s+r,所以呢,此时我们完全可以将s+r整体当做新的s,或者把它称作s',当然,此时s'很有可能已经超过了m+1,但我们就可以提取出其中(m+1)的部分来,与前面的式子合并,最后就变成了n=(m+1)*(r+1)+s'-(m+1),这就又归结到了最初的那个公式当中,所以千变万化逃不出开始的公式,也就是穷尽了所有的情况~
求k=(n-1)%(m+1),若k=0,则后手胜,若k!=0,则先手胜。
现在,趁热打铁来做两道题巩固一下吧:
1、南阳理工OJ的23题:取石子,最典型的巴什博奕,直接套模板
http://acm.nyist.net/JudgeOnline/problem.php?pid=23
AC代码:
#include <iostream> using namespace std; int main() { int t; int n,m; cin>>t; while(t--) { cin>>n>>m; int flag = n%(m+1); if(flag)cout<<"Win"<<endl; else cout<<"Lose"<<endl; } return 0; }2、杭电2149
http://acm.hdu.edu.cn/showproblem.php?pid=2149
在最基本的巴什博奕基础上有一定的变形,稍稍绕一下就出来了
AC代码:
#include <iostream> using namespace std; int main() { int m,n; while(cin>>m>>n) { if(m<=n)//如果m<=n的时候的话,先手一次性就可以取尽,因此此时从m到n的数字都是答案,这里要注意输出格式空格的问题,否则会PE { for(int i=m;i<=n;i++) { if(i==m)cout<<i; else cout<<" "<<i; } cout<<endl; } else //若m>n,则按照巴什博奕的原理,判断m%(n+1),若为0,则先手败,输出“none” { int flag = m%(n+1); if(flag==0)cout<<"none"<<endl; else { cout<<flag<<endl;//若果m%(n+1)不为0,则模之后的结果就是上文分析中的s,此时先手只要拿走这个s即可 } } } return 0; }