必备基础知识
1.博客https://blog.csdn.net/luomingjun12315/article/details/45479073
2.百度文库https://wenku.baidu.com/view/3d477e9f6bec0975f465e215.html?from=search
3.常见博弈游戏类型集合https://blog.csdn.net/qq_15714857/article/details/49691585
Bash Game(巴什博弈)
1)51nod1066:Bash游戏
简单Bash游戏模版题
题目:https://www.51nod.com/Challenge/Problem.html#!#problemId=1066
规则:每次最少拿1颗,最多拿K颗,拿到最后1颗石子的人获胜。
思路:通式n = (k + 1) * r + s
当s=0,r=1时n=k+1,特殊情况后者赢,证明参考博客https://blog.csdn.net/luomingjun12315/article/details/45479073
hdoj1846也是典型的Bash游戏模版题,此处就不再放ac代码了。
#include <iostream>
#include <cmath>
#include <string.h>
#include <algorithm>
using namespace std;
typedef long long ll;
int main()
{
ll t,n,k;
scanf("%lld",&t);
while(t--)
{
scanf("%lld %lld",&n,&k);
if(n%(k+1)==0)
printf("B\n");
else
printf("A\n");
}
return 0;
}
2)51nod1067:Bash游戏 V2
Bash游戏变形
题目:https://www.51nod.com/Challenge/Problem.html#!#problemId=1067
规则:每次只能拿1,3,4颗,拿到最后1颗石子的人获胜。
思路:打表找规律,见代码(我觉得和Bash博弈的思想没有太大关系吧,主要就是找规律了。)
#include <iostream>
#include <cmath>
#include <string.h>
#include <algorithm>
using namespace std;
typedef long long ll;
int main()
{
ll t,n;
scanf("%lld",&t);
while(t--)
{
scanf("%lld",&n);
if(n%7==0||n%7==2)
printf("B\n");
else
printf("A\n");
}
return 0;
}
3)51nod1068:Bash游戏 V3
Bash游戏变形
题目:https://www.51nod.com/Challenge/Problem.html#!#problemId=1068
规则:每次拿的数量只能是2的正整数次幂,比如(1,2,4,8,16....),拿到最后1颗石子的人获胜。
思路:打表找规律,见代码
#include <iostream>
#include <cmath>
#include <string.h>
#include <algorithm>
#define maxn 1005
using namespace std;
typedef long long ll;
int main()
{
ll t,i;
char a[maxn];
scanf("%lld",&t);
while(t--)
{
int sum=0;
cin>>a;
for(i=0;i<strlen(a);i++)
sum+=(a[i]-'0');
if(sum%3==0)
printf("B\n");
else
printf("A\n");
}
return 0;
}
斐波那契博弈
规则:每次拿的数量最少1个,最多不超过对手上一次拿的数量的2倍(A第1次拿时要求不能全拿走)。拿到最后1颗石子的人获胜。
证明参考:https://blog.csdn.net/acm_cxlove/article/details/7835016
1)51nod1070:Bash游戏 V4
Bash游戏变形&斐波那契博弈
题目:https://www.51nod.com/Challenge/Problem.html#!#problemId=1070
规则:即斐波那契博弈规则
思路:若n为斐波那契数,那么先手输,否则,后手输。
注意:1e+9大概是斐波那契数列的第44个,2^32比1e+9大一些,稍大于4e+9,故数组其实只需要开到60即可
hdoj2516也为斐波那契博弈的模版题,此处就不再单独放ac代码了。
#include <iostream>
#include <cmath>
#include <string.h>
#include <algorithm>
#define maxn 1000
#define inf 1e+9+10
using namespace std;
typedef long long ll;
int f[maxn]={0};
int main()
{
ll t,a,num,i;
f[1]=1;f[2]=2;
for(num=3;num<maxn;num++)
{
f[num]=f[num-1]+f[num-2];
if(f[num]>inf)
break;
}//num为44
scanf("%lld",&t);
while(t--)
{
bool flag=0;
scanf("%lld",&a);
for(i=1;i<=num;i++)
if(f[i]==a)
{
flag=1;//是斐波那契数的话先手败
break;
}
printf("%c\n",flag?'B':'A');
}
return 0;
}
威佐夫博弈
关键内容:必败态(奇异局势) 黄金分割
(0,0)(1,2)(3,5)(4,7)(6,10)。。。只要符合这种情况的输入,则先手败
黄金分割数:(sqrt(5.0)+1)/2
规则:每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子;二是可以在两堆中同时取走相同数量的石子。最后把石子全部取完者为胜者。
大佬对POJ模版威佐夫博弈题的解读(参考阅读!):https://www.cnblogs.com/jackge/archive/2013/04/22/3034968.html
hdoj1527、51nod1072和POJ的这道题都是模版题,此处就不再贴ac代码了。
1)hdoj2177:取(2堆)石子游戏
题目:http://acm.hdu.edu.cn/showproblem.php?pid=2177
规则:即威佐夫规则
!强调输出内容!:如果最后你是败者,则为0,反之,输出1,并输出使你胜的你第1次取石子后剩下的两堆石子的数量x,y,x<=y。如果在任意的一堆中取走石子能胜同时在两堆中同时取走相同数量的石子也能胜,先输出取走相同数量的石子的情况.
思路:见代码
#include <iostream>
#include <cmath>
#include <string.h>
#include <algorithm>
#include <stdlib.h>
#define maxn 1000005
using namespace std;
int fl[maxn];//fl[i]即必败态左侧数i对应的是第fl[i]个必败态
int fr[maxn];//右侧
int main()
{
int a,b,i,m,ta,tb;
double t=(sqrt(5.0)+1)/2.0;
memset(fl,-1,sizeof(fl));
memset(fr,-1,sizeof(fr));
for(i=0;i*t+i<maxn;i++)
{
m = int(t * i);
fl[m] = i;
fr[m + i] = i;
}
while(scanf("%d %d",&a,&b))//a<=b
{
if(a==0&&b==0)
break;
if(floor((b-a)*t)==a)//输入的(a,b)为必败态
{
printf("0\n");
continue;
}
else
printf("1\n");
ta=int(t*(b-a));
tb=ta+(b-a);
if(a>ta && b>tb)
printf("%d %d\n",ta,tb);//两堆石子中取走相同的石子数
if(fl[a]!=-1 && b>a+fl[a])
printf("%d %d\n",a,a+fl[a]);
if(fr[a]!=-1 && a!=b)//固定a且判重
printf("%d %d\n",a-fr[a],a);
if(fr[b]!=-1 && a>b-fr[b])
printf("%d %d\n",b-fr[b],b);
}
return 0;
}
2)51nod1185:威佐夫游戏 V2
高精度小数乘法和黄金分割数的精确求解
题目:https://www.51nod.com/Challenge/Problem.html#!#problemId=1185
规则:每次可以从一堆中取任意个或从2堆中取相同数量的石子,但不可不取。拿到最后1颗石子的人获胜。
思路:取黄金分割数的前28位,进行高精度小数乘法,注意代码实现
#include <iostream>
#include <cmath>
#include <string.h>
#include <algorithm>
using namespace std;
typedef long long ll;
int main()
{
ll t,a,b,k,ah,al;
scanf("%lld",&t);
while(t--)
{
ll mod=1e+9,gold[3]={618033988,749894848,204586834},ans[4];
scanf("%lld %lld",&a,&b);
if(a>b)
swap(a,b);
k=b-a;
if(k>=a||k*2<=a)//根据规律快速判断,非必败态
{
printf("A\n");
continue;
}
ah=k/mod;al=k%mod;
ans[3]=al*gold[2];
ans[2]=ah*gold[2]+al*gold[1]+ans[3]/mod;
ans[1]=ah*gold[1]+al*gold[0]+ans[2]/mod;
ans[0]=k+ah*gold[0]+ans[1]/mod;
printf("%c\n",ans[0]==a?'B':'A');
}
return 0;
}
注意:前三十位黄金分割数如果直接在c++程序中求的话会造成精度损失,结果出错!
故附求法:
百度百科上的求法:
#include <iostream>
using namespace std;
int main (void)
{
int d,e=2,i=0,j,k=10,N,m;
cout<<"请输入黄金分割数位数:\n",cin>>N,N++;
int *a=new int[2*N],*b=new int[N],*c=new int[2*N];
while(++i<2*N)a[i]=b[i/2]=c[i]=0;
for(cout<<"0.6",a[b[1]=m=2]=4;--N>1;)
for(b[m]=e,d=0,j=k;--j+1&&!d;)
{
for(c[i=2*m]=j*j%k,e=j*j/k;--i>m-2;e=d/k)c[i]=(d=e+b[i-m+1]*j)%k;
for(i=d=m-1;i<2*m&&a[i]<=c[i];i++)(a[i]<c[i])?d=0:1;
if(d)for(e=j<<1,cout<<j,i=1+2*m++;--i>m-2;)if((a[i]-=c[i])<0)a[i]+=k,a[i-1]--;
}
delete []a,delete []b,delete []c,cin.ignore(),cin.ignore();
return 0;
}
学姐写的代码(Java):
package dp;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
public class T {
public static void main(String[] args) {
BigDecimal n = new BigDecimal("5.0");
BigDecimal r = sqrt(n);
BigDecimal tmp = new BigDecimal(r.toString());
tmp = tmp.add(new BigDecimal("1.0"));
tmp = tmp.divide(new BigDecimal("2.0"));
System.out.println(tmp);
}
public static BigDecimal sqrt(BigDecimal num) {
if(num.compareTo(BigDecimal.ZERO) < 0) {
return BigDecimal.ZERO;
}
BigDecimal x = num.divide(new BigDecimal("2"), MathContext.DECIMAL128);
while(x.subtract(x = sqrtIteration(x, num)).abs().compareTo(new BigDecimal("0.0000000000000000000001")) > 0);
return x;
}
private static BigDecimal sqrtIteration(BigDecimal x, BigDecimal n) {
return x.add(n.divide(x, MathContext.DECIMAL128)).divide(new BigDecimal("2"), MathContext.DECIMAL128);
}
}
未完待续。。。。