博弈的题目接触的不是很多,先在这里简单总结一下三种最基础的博弈吧。以后等做到相应的题目了再补充。
有一堆石子,石子个数为n,两人轮流从石子中取石子出来,最少取一个,最多取m个。最后不能取的人输,问你最后的输赢情况。
这种就是可以直接判断必败态的问题。如果这堆石子少于或者等于m个,那么先手赢。如果石子数目为m+1个,那么先手必输,因为无论先手怎么拿石子,后手都可以直接把剩下的石子全部拿走。如果石子数目为m+1<n<2(m+1),那么先手就可以拿走n-(m+1)个石子,使得对手面对m+1的必败态,这样先手必赢。所以如此推算下去,我们就知道当n%(m+1)==0时,先手输,否则先手赢。
先撸几道水题找点感觉吧:
题目大意:拍卖一块土地,两个人轮流加价,每次叫价在1~N元范围内,当大于等于M元时,就算竞拍成功。每次都是Lee先叫价,如果最后能够竞拍成功,输出第一次叫价时报的价格。
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<map> #include<queue> #include<stack> #include<vector> #include<set> #include<ctype.h> #include<algorithm> #include<string> #define PI acos(-1.0) #define maxn 1000 #define INF 1<<25 #define mem(a, b) memset(a, b, sizeof(a)) typedef long long ll; using namespace std; int main () { int n, m; while(~scanf("%d%d", &m, &n)) { int t = m % (n + 1); if (t == 0) puts("none"); else { if (n >= m) // 如果N一开始就大于M,所有大于等于M的叫价都可以 { for (int i = m; i < n; i++) printf("%d ", i); printf("%d\n", n); } else printf("%d\n", t); //否则就直接输出t } } return 0; }
就是最基本的取石子游戏。
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<map> #include<queue> #include<stack> #include<vector> #include<set> #include<ctype.h> #include<algorithm> #include<string> #define PI acos(-1.0) #define maxn 1000 #define INF 1<<25 #define mem(a, b) memset(a, b, sizeof(a)) typedef long long ll; using namespace std; int main () { int c, n , m; cin>>c; while(c--) { scanf("%d%d", &n, &m); if (n % (m +1) == 0) puts("Rabbit"); else puts("Grass"); } return 0; }
可以发现,前面几组奇异局势为(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 方括号表示取整函数)
详见:威佐夫博奕(百度百科)
接下来就撸几道题目吧,形式还是挺多的。
题目大意:就是威佐夫博奕。。。
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<map> #include<queue> #include<stack> #include<vector> #include<set> #include<ctype.h> #include<algorithm> #include<string> #define PI acos(-1.0) #define maxn 1000 #define INF 1<<25 #define mem(a, b) memset(a, b, sizeof(a)) typedef long long ll; using namespace std; int main () { int a, b; while(~scanf("%d%d", &a, &b)) { if (a > b) swap(a, b); // 交换a,b 保证a比b要小 int k = b - a; int n = (1 + sqrt(5.0)) / 2 * k; // 通过k来计算a。 if (a == n) puts("0"); else puts("1"); } return 0; }
同样是取石头问题,不过如果先手能赢,要输出第一次取石子后所剩的情况。(如果在任意的一堆中取走石子能胜同时在两堆中同时取走相同数量的石子也能胜,先输出取走相同数量的石子的情况)
这道题目要输出威佐夫博弈从非奇异局势到奇异局势的转变方案。比直接判断复杂了很多,其实数据量不大,可以打表。不过还是本文采用推理的方式来解这道题。
思考过程:
1. 先判断能否从两堆中取出相同的石头,达到目的。一直写的很挫,最后用了枚举才过的。。后来看别人写的这块内容,又一次觉得自己好弱
2. 判断从一堆中取出石头,这里其实可以分为三种。假设一开始的时候为(a,b)。第一种是从b当中取走少量的,变为(a,b - k)。第二种是从b中取走大量的,变为(b - t, a)。第三种是从a当中取走一些,变为(a - t,b)
这里我竟然傻逼的用黄金分割率去做。用b*0.618来。必然丢精度啊。。后来还是上网看了很多这方面的文章,不过写的都不是很详细。估计是我太弱。。
(PS 这道题是special judge。。。。我之前还想着怎么把重复输出的去掉。。。坑爹)·
// 威佐夫博弈 + 输出方案 #include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<map> #include<queue> #include<stack> #include<vector> #include<set> #include<ctype.h> #include<algorithm> #include<string> #define PI acos(-1.0) #define maxn 1000 #define INF 1<<25 #define mem(a, b) memset(a, b, sizeof(a)) typedef long long ll; using namespace std; int main () { int a, b; while(scanf("%d%d", &a, &b)) { if (a == 0 && b == 0) break; int k = b - a; int n = (1 + sqrt(5.0)) / 2 * k; if (a == n) puts("0"); else { puts("1"); if (a - n == b - n - k) printf("%d %d\n", n, n + k); //两者之间的差值不变 if (a == 0) puts("0 0"); //如果a等于0了,差值只能为0,即为(0,0)。 for (int i = 1; i <=b; i++) //枚举a和b之间的差值,可能从1一直到b - 1 { n = (1 + sqrt(5.0)) / 2 * i; int tmp = n + i; if (n == a) printf("%d %d\n", n, tmp); if (tmp == a) printf("%d %d\n",n, tmp); if (tmp == b) printf("%d %d\n", n, b); } } } return 0; }
// 尼姆博奕 #include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<map> #include<queue> #include<stack> #include<vector> #include<set> #include<ctype.h> #include<algorithm> #include<string> #define PI acos(-1.0) #define maxn 1000 #define INF 1<<25 #define mem(a, b) memset(a, b, sizeof(a)) typedef long long ll; using namespace std; int main () { int m, sum = 0, n; while(~scanf("%d", &m)) { sum = 0; while(m--) { scanf("%d", &n); sum ^= n; } if (!sum) puts("No"); // (0, 0, 0) 为必败态,即异或之后为0为必败态 else puts("Yes"); } return 0; }
/* http://acm.hdu.edu.cn/showproblem.php?pid=2176 尼姆博奕 并且求输出方案 */ #include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<map> #include<queue> #include<stack> #include<vector> #include<set> #include<ctype.h> #include<algorithm> #include<string> #define PI acos(-1.0) #define maxn 200000 #define INF 1<<25 #define mem(a, b) memset(a, b, sizeof(a)) typedef long long ll; using namespace std; int sg[maxn+10]; int main () { int m; while(scanf("%d", &m) && m) { int sum = 0; for (int i = 0; i < m; i++) { scanf("%d", sg + i); sum ^= sg[i]; } if (!sum) puts("No"); else { puts("Yes"); for (int i = 0; i < m; i++) { // sum = sg[0] ^ sg[1] ^ sg[2] ^ …… ^sg[m - 1] int s = sum ^ sg[i]; // s = sum ^ sg[i] 我们可以知道,异或符合交换律,而两个相同的数异或后为0,而一个数与0异或不改变 //因此,s相当于除了sg[i]之外的所有堆的个数异或 if (s < sg[i]) printf("%d %d\n", sg[i], s);//如果s<sg[i]。便可以取走sg[i] - s颗石头,变为s颗。两两异或之后为0 } } } return 0; }