三种基本博弈(巴什博弈 威佐夫博奕 尼姆博弈)

所有的博弈都是找到必败态,

如果当前的状态是必败态那么无论怎么操作都无法使游戏变成必胜态,

反之如果是必胜态则必然存在一步操作还使游戏变成必胜态。

下面说的就是三种基本的博弈:巴什博弈 威佐夫博奕 尼姆博弈

 一:巴什博弈

有一堆石子,石子个数为n,两人轮流从石子中取石子出来,最少取一个,最多取m个。最后不能取的人输,问你最后的输赢情况。

这种就是可以直接判断必败态的问题。如果这堆石子少于或者等于m个,那么先手赢。如果石子数目为m+1个,那么先手必输,因为无论先手怎么拿石子,后手都可以直接把剩下的石子全部拿走。如果石子数目为m+1<n<2(m+1),那么先手就可以拿走n-(m+1)个石子,使得对手面对m+1的必败态,这样先手必赢。所以如此推算下去,我们就知道当n%(m+1)==0时,先手输,否则先手赢

n*m+1)为必败态。先手取x,另外一个人就可以取y,使得取的石子的总数是m+1

二:威佐夫博弈

          有两堆石子,石子数目分别为n和m,现在两个人轮流从两堆石子中拿石子,每次拿时可以从一堆石子中拿走若干个,也可以从两中拿走相同数量的石子,拿走最后一刻石子的人赢。

            可以发现,前面几组奇异局势为(a,b) :(0,0)、(1,2)、(3,5)、(4,7)、(6,10)、(8,13)、(9,15)、(11,18)、(12,20)

这其中第k组的a为前面没有出现过的最小非负整数,而b = a + k 

两个人如果都采用正确操作,那么面对非奇异局势,先拿者必胜;反之,则后拿者取胜。
那么任给一个局势(a,b),怎样判断它是不是奇异局势呢?我们有如下公式:
ak =[k(1+√5)/2],bk= ak + k (k=0,1,2,...n 方括号表示取整函数)

详见:威佐夫博奕(百度百科)

取石子游戏 http://acm.bnu.edu.cn/bnuoj/problem_show.php?pid=1186

题目大意:威佐夫博奕

  1. #include<iostream>  
  2. #include<cstdio>  
  3. #include<cstring>  
  4. #include<cmath>  
  5. #include<map>  
  6. #include<queue>  
  7. #include<stack>  
  8. #include<vector>  
  9. #include<set>  
  10. #include<ctype.h>  
  11. #include<algorithm>  
  12. #include<string>  
  13. #define PI acos(-1.0)  
  14. #define maxn 1000  
  15. #define INF 1<<25  
  16. #define mem(a, b) memset(a, b, sizeof(a))  
  17. typedef long long ll;  
  18. using namespace std;  
  19. int main ()  
  20. {  
  21.     int a, b;  
  22.     while(~scanf("%d%d", &a, &b))  
  23.     {  
  24.         if (a > b) swap(a, b);  // 交换a,b 保证a比b要小  
  25.         int k = b - a;  
  26.         int n = (1 + sqrt(5.0)) / 2 * k; // 通过k来计算a。  
  27.         if (a == n) puts("0");  
  28.         else puts("1");  
  29.     }  
  30.     return 0;  
  31. }  


取(2堆)石子游戏 http://acm.hdu.edu.cn/showproblem.php?pid=2177

同样是取石头问题,不过如果先手能赢,要输出第一次取石子后所剩的情况。(如果在任意的一堆中取走石子能胜同时在两堆中同时取走相同数量的石子也能胜,先输出取走相同数量的石子的情况)

这道题目要输出威佐夫博弈从非奇异局势到奇异局势的转变方案。比直接判断复杂了很多,其实数据量不大,可以打表。不过还是采用推理的方式来解这道题。

思考过程:

判断从一堆中取出石头,这里其实可以分为三种。假设一开始的时候为(a,b)    (b>a) 即默认括号里面第二个数大于第一个数 。第一种是从b当中取走少量的,变为(a,b - k)。第二种是从b中取走大量的,变为(b - t, a)。第三种是从a当中取走一些,变为(a - t,b)

  1. // 威佐夫博弈 + 输出方案  
  2. #include<iostream>  
  3. #include<cstdio>  
  4. #include<cstring>  
  5. #include<cmath>  
  6. #include<map>  
  7. #include<queue>  
  8. #include<stack>  
  9. #include<vector>  
  10. #include<set>  
  11. #include<ctype.h>  
  12. #include<algorithm>  
  13. #include<string>  
  14. #define PI acos(-1.0)  
  15. #define maxn 1000  
  16. #define INF 1<<25  
  17. #define mem(a, b) memset(a, b, sizeof(a))  
  18. typedef long long ll;  
  19. using namespace std;  
  20. int main ()  
  21. {  
  22.     int a, b;  
  23.     while(scanf("%d%d", &a, &b))  
  24.     {  
  25.         if (a == 0 && b == 0) break;  
  26.         int k = b - a;  
  27.         int n = (1 + sqrt(5.0)) / 2 * k;  
  28.         if (a == n) puts("0");  
  29.         else  
  30.         {  
  31.             puts("1");  
  32.             if (a - n == b - n - k) printf("%d %d\n", n, n + k);    //两者之间的差值不变  
  33.             if (a == 0) puts("0 0");    //如果a等于0了,差值只能为0,即为(0,0)。  
  34.             for (int i = 1; i <=b; i++) //枚举a和b之间的差值,可能从1一直到b - 1  
  35.             {  
  36.                 n = (1 + sqrt(5.0)) / 2 * i;  
  37.                 int tmp = n + i;  
  38.                 if (n == a) printf("%d %d\n", n, tmp);  
  39.                 if (tmp == a) printf("%d %d\n",n, tmp);  
  40.                 if (tmp == b) printf("%d %d\n", n, b);  
  41.             }  
  42.         }  
  43.     }  
  44.     return 0;  
  45. }  

三:尼姆博奕

定义:有三堆各若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少取一个,多者不限,最后取光者得胜。
当剩下一堆石头时,一定是必胜态。当剩下两堆相同的石头时(n,n),一定是必败态。只要你取x,对手在另一堆里面取x,会得到(n - x,n - x)。至于三堆的就比较麻烦,可以知道(1,2,3)是必败态。
不过最后有个神奇的结论,就是当每堆石头取异或之后,不为0代表着必胜态。为0代表着必败态。

证明:
1、最后的状态,全为零,显然成立;
2、对于某个局面(a1,a2,...,an),若a1^a2^...^an<>0,一定存在某个合法的移动,将ai改变成ai'后满足a1^a2^...^ai'^...^an=0。不妨设a1^a2^...^an=k,则一定存在某个ai,它的二进制表示在k的最高位上是1(否则k的最高位那个1是怎么得到的)。这时ai^k<ai一定成立。则我们可以将ai改变成ai'=ai^k,此时a1^a2^...^ai'^...^an=a1^a2^...^an^k=0。
3、对于某个局面(a1,a2,...,an),若a1^a2^...^an=0,一定不存在某个合法的移动,将ai改变成ai'后满足a1^a2^...^ai'^...^an=0。因为异或运算满足消去率,由a1^a2^...^an=a1^a2^...^ai'^...^an可以得到ai=ai'。所以将ai改变成ai'不是一个合法的移动。证毕。

题目大意:给你n堆石子,每堆个数不同,两个人轮流从任意一堆当中取任意个石子。当其中一个人取走最后一堆石头时,获胜。问先手能不能获胜。如果能够获胜,求出能够获胜第一次取石子的情况。(a,b) a为该堆石头数,b为取完之后的数。
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. /* 
  2.     http://acm.hdu.edu.cn/showproblem.php?pid=2176  
  3.     尼姆博奕 并且求输出方案 
  4. */  
  5.   
  6. #include<iostream>  
  7. #include<cstdio>  
  8. #include<cstring>  
  9. #include<cmath>  
  10. #include<map>  
  11. #include<queue>  
  12. #include<stack>  
  13. #include<vector>  
  14. #include<set>  
  15. #include<ctype.h>  
  16. #include<algorithm>  
  17. #include<string>  
  18. #define PI acos(-1.0)  
  19. #define maxn 200000  
  20. #define INF 1<<25  
  21. #define mem(a, b) memset(a, b, sizeof(a))  
  22. typedef long long ll;  
  23. using namespace std;  
  24. int sg[maxn+10];  
  25. int main ()  
  26. {  
  27.     int m;  
  28.     while(scanf("%d", &m) && m)  
  29.     {  
  30.         int sum = 0;  
  31.         for (int i = 0; i < m; i++)  
  32.         {  
  33.             scanf("%d", sg + i);  
  34.             sum ^= sg[i];  
  35.         }  
  36.         if (!sum) puts("No");  
  37.         else  
  38.         {  
  39.             puts("Yes");  
  40.             for (int i = 0; i < m; i++)  
  41.             {  
  42.                                      // sum = sg[0] ^ sg[1] ^ sg[2] ^ …… ^sg[m - 1]  
  43.                 int s = sum ^ sg[i]; // s = sum ^ sg[i] 我们可以知道,异或符合交换律,而两个相同的数异或后为0,而一个数与0异或不改变  
  44.                                      //因此,s相当于除了sg[i]之外的所有堆的个数异或  
  45.                 if (s < sg[i]) printf("%d %d\n", sg[i], s);//如果s<sg[i]。便可以取走sg[i] - s颗石头,变为s颗。两两异或之后为0  
  46.             }  
  47.         }  
  48.     }  
  49.     return 0;  
  50. }


©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页