第一行国际惯例咕咕咕。
第二行——博弈是很有趣的,只不过我不会而已。
第三行——我现在怀疑是我的智商问题导致我看了很多博客都不懂nim怎么证明的。(为什么他们的证明方法都一样啊呜呜呜)
前言:博弈的题应该可以先考虑找找规律?从小的开始推必胜必败态然后数学归纳试试?(自己瞎总结的
后续:我已经不管什么证明不证明了,我现在已经愉快的决定记住结论来解题了。
证明留个坑吧,不一定什么时候补坑的那种。
一、巴什博弈(同余定理)
巴什博弈:只有一堆n个物品,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个。最后取光者得胜。
这个问题看起来很深奥,其实理解透了——也挺深奥的(一个蒟蒻如是说)。
证明:
先剖析题意——每次可以取1~m个,那存在如下几种情况:
1. n%(m+1)==0,此时,无论先手取几个,后手都可以取i个使每轮是m+1个,最终先手必输。
2. n%(m+1)!=0,此时先手先取n%(m+1)个,后手取k个,先手再取l个使k+l=m+1,最后一次即为先手取得。
下附几个基础练习题
1.HDU4764 2.HDU2149 3.HDU2188 4.HDU1847 5.HDU2897
由于HDU2897略有变形,下附代码(代码写的丑就将就一下叭):
#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
int n,p,q;
while(~scanf("%d%d%d",&n,&p,&q))
{
if(n % (p+q) == 0) printf("WIN\n");
else
{
if(n % (p+q) <= p) printf("LOST\n");
else if(n % (p+q) > p && n % (p+q) <= q) printf("WIN\n");
else printf("WIN\n");
}
}
return 0;
}
二、尼姆游戏(异或定理)
尼姆游戏:有n堆石子,双方轮流取任意一堆石子中的任意数量(不能不取),且每个人都采用对其最有利的策略,最后取走石子的人为胜。
尼姆游戏的特点:1.两个人。2.给定了转移状态。3.状态有限。4.轮流进行。5.结束状态明确。
证明:
首先我们定义P为先手必胜,N为先手必败。那么可得(对先手而言):
1.所有能导致N状态的为P 2.所有能导致P状态的为N 3.所有P状态为P 4.所有N状态为N
由当前状态只能往P或N两个状态转移。
n堆石子,①若全部为0,则先手必输。②若其中一堆>0,其余为0则先手必胜。③若所有石子相同,当为偶数堆则先手必输,因为无论先手拿多少,后手都可以从另一堆拿相同的个数;当为奇数,先手必胜,因为先手可先移走一堆,后手变先手。④由③,可以推出,当有偶数堆,并且石子的个数可以找到相同的堆,即可分为两部分,不论前手拿多少,后手都可以拿对称堆的相同个数,最终前手没有石子取。⑤根据④,我们可以将所有的成双堆数移除,变为全部为不相同的石子的堆,对于这些石子,不论怎样取,都会变成偶数堆的状态/全为不同状态,对于所有的石子,进行异或的异或和,一定至少有一堆的石子最高位为异或和的最高位的1(有的数量一定为奇数堆),这里先留个坑,我不知道该怎么往下证明了呜呜呜,现在只会用结论。
结论:①若所有数二进制异或为0,则先手必输,否则,则先手必胜。②若先手必胜,则先手的第一步可以为移走第i堆剩异或和异或第i堆石子个数。
下附练习题:1.洛谷P2197(代码以它为例)2.POJ2234 3.HDU2176 4.HDU1850 5.HDU1907
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n,a;
scanf("%d",&n);
int res = 0;
for(int i = 0; i < n; ++i)
scanf("%d",&a),res ^= a;
if(res == 0) printf("No\n");
else printf("Yes\n");
}
return 0;
}
三、威佐夫博弈(黄金分割)
威佐夫游戏:有两堆各若干个物品,两个人轮流从任一堆取至少一个或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。
结论:若初始值为x,y,z = abs(x-y),w = (int)(sqrt(5)+1)*z/2。如果,w = min(x,y),那么先手必败,否则先手必胜。
本代码以POJ1067为例
#include <iostream>
#include <cstdio>
#include <cmath>
#define ll long long
using namespace std;
int main()
{
ll x,y;
while(~scanf("%lld%lld",&x,&y))
{
if(x > y) swap(x,y);
ll z = y-x;
ll q = floor((sqrt(5.0)+1.0)*z/2.0);
if(q == x) printf("0\n");
else printf("1\n");
}
return 0;
}
四、Fibonacci博弈
斐波那契博弈:给定n个石子,A、B两人轮流取石子,A先取,可以任取石子,但第一次不能全取完,而且至少取一个。每次所取石子数必须大于或等于1,且小于等于对手刚取的石子数的两倍。取到最后一个石子的为胜者。
结论:当为Fibonacci数时,为先手必败态。
证明:首先:Zeckendorf定理表示任何正整数都可以表示成若干个不连续的斐波那契数(不包括第一个斐波那契数)之和。
可利用数学归纳法证明该结论。(我再次留坑
题目:HDU2516
#include <iostream>
#include <cstdio>
#include <set>
#define ll long long
using namespace std;
ll f[55];
set<ll>s;
void fibo()
{
f[0] = 0;
f[1] = 1;
s.insert(0);
s.insert(1);
for(int i = 2; i < 55; ++i)
f[i] = f[i-1] + f[i-2],s.insert(f[i]);
}
int main()
{
ll n;
fibo();
while(~scanf("%lld",&n))
{
if(n == 0) break;
if(s.count(n) == 1) printf("Second win\n");
else printf("First win\n");
}
return 0;
}
PS:四月写成了巴什的博客,太基础简单。今天想起其他博弈,刚好就学习+重写博客了(一篇博客单讲一个巴什未免太缺少内容)。
欢迎指出错误qwq
欢迎给我讲解博弈呜呜呜