您的位置:首页 > 其它

几个博弈小题总结 脑子是个好玩意,我也想有一个。

2017-03-01 17:53 281 查看
序 : 下面的例题只给了题源,没给题目链接,但是都可以根据对应的题号找到题目来提交代码,例如HDU 2897就是hdoj的2897题。然后题意大概都是我YY之后的,也许有些描述错误,然后某些解题思路来自网上大神的博客,如果有觉得侵犯了权利请联系我(ps,本来就是写给自己看的,巨丑,然后每个地方好放,放到博客怎么也会给人看到的,请多多包涵)

博弈真的是智力题啊。。。

1, HDU 2897

题意:一堆n个石子,每次最多取q个,最少取p个,切最后不少于p个时必须一次性取完。谁后取完者输。

解题思路: 那么就是谁先取到剩p个时候就赢了,少于p个的话一定输,因为必须取完,如果谁当前遇到0个,赢,大于p小于p+q个是赢,因为先手总是可以想办法给后手留下p个,让他不得不拿。推理发现n个和n-(p+q)输赢一样,所以首先可对n%=(p+q)。

//HDU 2987
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n, p, q;
while(scanf("%d%d%d", &n, &p, &q) != EOF)
{
if(p > q) swap(p, q); //应该没有和题意不合的数据,这句话多余
n %= (p + q);
if(n <= p && n != 0) puts("LOST");
else puts("WIN");
}
return 0;
}


2, HDU 2516

题意:1堆石子有n个,两人轮流取.先取者第1次可以取任意多个,但不能全部取完.以后每次取的石子数不能超过上次取子数的2倍。取完者胜.先取者负输出”Second win”.先取者胜输出”First win”。

解题思路: Fib尼姆博弈,这个问题我是真的说不清楚,然后对于这个博弈结论的证明感觉也是比较迷糊的,反正记住这个典型模型就行了,具体证明可以看世界冠军cxlove神的博客:http://blog.csdn.net/acm_cxlove/article/details/7835016

//HDU 2516
#include <bits/stdc++.h>
using namespace std;
int fib[50];
int main()
{
fib[0] = 1;
fib[1] = 2;//这里和普通fib不一样,自己推一下就明白了
for(int i = 2; i < 45; i++) fib[i] = fib[i-1] + fib[i-2]; //预处理fib
int n;
while(scanf("%d", &n)!=EOF)
{
if(n == 0) break;
int i = 0;
for(i = 0; i < 45; i++){
if(fib[i] == n) break;
}
if(i < 45) puts("Second win");
else puts("First win");
}
return 0;
}


3,HDU 2147

题意:一个n*m的表格,起始位置为右上角,目标位置为左下角,甲先开始走,走的规则是可以向左,向下或者向左下(对顶的)走一格。谁先走到目标位置谁就胜利。在甲乙都采用最佳策略的时候,先走者能否获胜。

下面的节选自这位大神博客的一段话

解题思路: 巴什博奕的模型,关键为画出PN图,那什么是PN图呢?等等先介绍一下巴什博奕: 只有一堆n个物品,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个。最后取光者得胜。
显然,如果n=m+1,那么由于一次最多只能取m个,所以,无论先取者拿走多少个,后取者都能够一次拿走剩余的物品,后者取胜。因此我们发现了如何取胜的法则:如果
n=(m+1)r+s,(r为任意自然数,s≤m),那么先取者要拿走s个物品,如果后取者拿走k(≤m)个,那么先取者再拿走m+1-k个,结果剩下(m+1)(r-1)个,以后保持这样的取法,那么先取者肯定获胜。总之,要保持给对手留下(m+1)的倍数,就能最后获胜。那么这个时候只要n%(m+1)!=0,先取者一定获胜。
这个游戏还可以有一种变相的玩法:两个人轮流报数,每次至少报一个,最多报十个,谁能报到100者胜。
分析此类问题主要放法是:P/N分析:
P点:即必败点,某玩家位于此点,只要对方无失误,则必败;
N点:即必胜点,某玩家位于此点,只要自己无失误,则必胜。
三个定理:
一、所有终结点都是必败点P(上游戏中,轮到谁拿牌,还剩0张牌的时候,此人就输了,因为无牌可取);
二、所有一步能走到必败点P的就是N点;
三、通过一步操作只能到N点的就是P点;
巴什博弈的一个最重要的特征就是只有一堆。然后就在其中改,要么在范围内不规定个数,要么就规定只能取几个,再要么就倒过来,毕竟是最简单的博弈,代码相对而言较短额~


然后根据PN规则我们可以分析出这个题的PN图如下:

例如,n = 8, m = 9时

NNNNNNNNN

PNPNPNPNP

NNNNNNNNN

PNPNPNPNP

NNNNNNNNN

PNPNPNPNP

NNNNNNNNN

PNPNPNPNP

初始点(1,1)为N所以输出Wonderful!

从这里例子就可以很清楚得看出当n和m都为奇数时,初始点(1,1)才会是P。

所以这个问题就判断了n和m是否同奇。

//HDU 2147
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n, m;
while(scanf("%d%d", &n, &m) != EOF)
{
if(n == 0 && m == 0) break;
if(n % 2 == 1 && m % 2 == 1){
printf("What a pity!\n");
}
else{
printf("Wonderful!\n");
}
}
return 0;
}


4, HDU 3951

题意:n个硬币放成一圈,每次最多取连续k个,不能不取。最后取完者胜。

解法:一个非常容易想到的博弈了。n个硬币,不论n是奇数偶数,后手总能够在第一轮把它变成对称两部分的状态,对称状态下后手肯定赢。那么先手能赢只能是k>=n或者每次只能取一个且n是奇数。

#include <bits/stdc++.h>
using namespace std;
int main()
{
int T, n, k, ks = 0;
scanf("%d", &T);
while(T--){
scanf("%d%d", &n, &k);
if(k >= n || (k == 1 && n & 1)) printf("Case %d: first\n", ++ks);
else printf("Case %d: second\n", ++ks);
}
return 0;
}


5,HDU 3389 变相的Nim博弈

题意:给出n个盒子,编号1–n,在满足b>a && (a+b)%2==1 && (a+b)%3==0 && b不为空,的情况下从b中取出任意多个石子放入a中,不能操作者输,求先手输赢?

解法:首先,要满足(a+b)%3==0 ,既为3的倍数,又(a+b)%2==1,既为奇数,所以(a+b)为 3 , 6 , 9, 15 , 21…依次+6,发现周期为6。在一个周期 0 - - 6中。只有(1,2)(3 ,6 )(4,5)满足条件,那么就可以从 2 6 5中取出其中石子,变为nim游戏。所以只要求 i%6 == 0 || 1 || 5,的值异或就行。

这个题最脑洞是找到周期转化成nim求解。

代码可以这样写

#include <bits/stdc++.h>
using namespace std;
int main(){
int T, n, ks = 0;
scanf("%d", &T);
while(T--){
scanf("%d", &n);
int SG = 0;
for(int i = 1; i <= n; i++){
int x;
scanf("%d", &x);
if(i%6 == 0 || i%6 == 2 || i%6 == 5){
SG ^= x;
}
}
if(SG == 0){
printf("Case %d: Bob\n", ++ks);
}
else{
printf("Case %d: Alice\n", ++ks);
}
}
return 0;
}


6, HDU 1517

题意: 你和一个人玩游戏,给你一个数字n,每次操作可以从2~9中任选一个数字,并把它与p相乘,(游戏开始时p=1)两人轮流操作,当一个人操作完后p>=n,这个人就是胜者。

解法:这就非常的神了,上面已经说了PN图了,并且假设我们也会画PN图了,但是这个题是画不出来上面那个题那种图 然后去找规律的(其实也是有规律的,但是我不会),我们只能通过计算PN值来判断。由于每次都是从p=1开始的,所以只要判断每个游戏中1为必败点还是必胜点即可。(以下各式 / 均为取上整)依照上面所提到的算法(就是PN图的规则,下面一会我再重点把这个算法摆出来),将终结位置,即[n,无穷]标记为必败点;然后将所有一步能到达此必败段的点标记为必胜点,即[n/9,n-1]为必胜点;然后将只能到达必胜点的点标记为必败点,即[n/9/2,n/9-1]为必败点;重复上面2个步骤,直至可以确定1是必胜点还是必败点。

#include <bits/stdc++.h>
using namespace std;
int main()
{
int n, x;
while(scanf("%d", &n) != EOF)
{
for(x = 0; n > 1; x++){
if(x&1) n = ceil((n*1.0/2));
else n = ceil((n*1.0/9));
}
puts(x&1?"Stan wins.":"Ollie wins.");
}
return 0;
}


求PN图算法:

算法实现:
步骤1:将所有终结位置标记为必败点(P点);(终结位置指的是不能将游戏进行下去的位置)
步骤2:将所有一步操作能进入必败点(P点)的位置标记为必胜点(N点)
步骤3:如果从某个点开始的所有一步操作都只能进入必胜点(N点) ,则将该点标记为必败点(P点) ;
步骤4:如果在步骤3未能找到新的必败(P点),则算法终止;否则,返回到步骤2。


7, BNUOJ 4353

只是觉得这个题目的分析非常有意思,看到了就记录下来了。

题意:给出一个m*n的矩阵,每次可以选择一个点,把其右上角的格子全部删去,谁删掉了最左下角的格子的话为输,求先手输赢。

解法:

首先看 1 * n 的格子,出了 1 * 1 的其他的都先手赢,因为先手可以一次取完只给队友留校左下一个。2 * n 的格子,先手赢。看 2 * 2 的,先手必定先取右上一个,那么剩下的后手不能一次取完,后手取完先手取剩下的给后手留给左下的。2 * 3 的先手赢,前面知 2 * 2 的先手赢,那么谁能取到面对 2 * 2 的则必胜,先手必先取最右下一个,那么不论后手怎么取都输。2 * n 的,都是先手赢,总可以推到前面的,而前面无论哪个都是先手赢。所以。。

3 * n 的,先手赢,首先先手一定不能一次变成 2 * n 的,因为后手面对2 * n 的则先手必输,那么先手可以取到 2 * n 多一个。那么他必胜。推理发现除了 1 * 1 的所有情况都是先手必胜。所以太简单就不给代码了。

8,Updating。。。

后记:完成得比较匆忙,有错误希望大家指出,谢谢QAQ。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: