今天的知识简直不要太有趣——博弈论(聪明的找规律)
重中之重 Nim博弈
(网上很多博客在讲,都很详细)
- 巴什博弈
- 威佐夫博奕
- 斐波那契博弈
- 阶梯博弈
第二类尼姆博弈
附上众多极好理解的讲解
多种博弈类型
多种博弈类型及经典例题
哇哇哇强推SG函数详解
大神论文
组合游戏略述——浅谈SG游戏的若干拓展及变形
王晓珂《解析一类组合游戏》
今天的题目都相对来说比较有(容)意(易)思
A 典型的Nim博弈+简单的sg打表即可
B&C 巴什博弈 注意对(m+1)取模,m+1什么你懂得
D 结果极其弱智,但需要思考的地方还很多,估计这种游戏策略就是先发制人吧
E 同A,只不过sg打表稍微复杂一些,这里引入两个模板
递推形式
//f[]:可以取走的石子个数
//sg[]:0~n的SG函数值
//hash[]:mex{}
int f[N],sg[N],hash[N];
void getSG(int n)
{
int i,j;
memset(sg,0,sizeof(sg));
for(i=1;i<=n;i++)
{
memset(hash,0,sizeof(hash));
for(j=1;f[j]<=i;j++)
hash[sg[i-f[j]]]=1;
for(j=0;j<=n;j++) //求mes{}中未出现的最小的非负整数
{
if(hash[j]==0)
{
sg[i]=j;
break;
}
}
}
}
dfs形式,处理数据较大
//DFS
//注意 S数组要按从小到大排序 SG函数要初始化为-1 对于每个集合只需初始化1遍
//n是集合s的大小 S[i]是定义的特殊取法规则的数组
int s[110],sg[10010],n;
int SG_dfs(int x)
{
int i;
if(sg[x]!=-1)
return sg[x];
bool vis[110];
memset(vis,0,sizeof(vis));
for(i=0;i<n;i++)
{
if(x>=s[i])
{
SG_dfs(x-s[i]);
vis[sg[x-s[i]]]=1;
}
}
int e;
for(i=0;;i++)
if(!vis[i])
{
e=i;
break;
}
return sg[x]=e;
}
注意二者都有相同的跳出循环部分
if(hash[j]==0)
{
sg[i]=j;
break;
}
F 对称的几何图形
⇒
⇒
分成两堆
⇒
⇒
Nim博弈
G 斐波那契博弈 套模板吧,证明太难了qaq
H sg打表,由于存在分成两堆的情况,要进行拆分重新考虑if(j!=0&&j!=i)vis[sg[j]^sg[i-j]]=true;//拆分
也可以dfs实现
递推式求sg函数代码+解答
#include<cstdio>
#include<cstring>
#include<set>
using namespace std;
const int maxn=1e6+5;
int a[maxn];
/*
int sg[1000];
void init()//sg打表
{
// memset(sg,0,sizeof(sg));
sg[0]=0;
sg[1]=1;
for(int i=2;i<=1000;i++)
{
bool vis[1010]={false};
for(int j=0;j<=i;j++)
{
vis[sg[j]]=true;//取石子
if(j!=0&&j!=i)vis[sg[j]^sg[i-j]]=true;//拆分
}
int j=0;
while(vis[j]!=0)j++;
sg[i]=j;
}
}
int main()
{
// memset(sg,-1,sizeof(sg));
init();
for(int i=1;i<=1000;i++)
{
printf("%d\t",sg[i]);
if(i%4==0)printf("\n");
}
}
*/
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int n;
scanf("%d",&n);;
int temp,ans=0;
for(int i=0; i<n; i++){
scanf("%d",&temp);
int t=temp;
if(temp%4==3)
t=temp+1;
else if(temp%4==0)
t=temp-1;
ans^=t;
}
if(ans){
printf("Alice\n");
}
else{
printf("Bob\n");
}
}
return 0;
}
I 看了大神的思路,感觉像是阶梯博弈吧,重点是找到规律(太难了qaq)后将原来的箱子化为两堆,并对奇数项进行Nim博弈操作。
代码
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int main()
{
int t,k=0,x,n;
scanf("%d",&t);
while(++k<=t)
{
int ans=0;
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
scanf("%d",&x);
if(i%6==0||i%6==2||i%6==5)//if(!(i%6==1||i%6==3||i%6==4))
ans^=x;
}
if(ans)
printf("Case %d: Alice\n",k);
else
printf("Case %d: Bob\n",k);
}
return 0;
}
K 求第一步的方案个数(异或和的唯一性)。博弈中异或和的极致使用,加深了对XOR的理解。
若a1^a2^…^an!=0,一定存在某个合法的移动,
将ai改变成ai’后满足a1^a2^…^ai’^…^an=0。
若a1^a2^…^an=k,则一定存在某个ai,
它的二进制 表示在k的最高位上是1(否则k的最高位那个1是怎么得到的)。
这时ai^k
js=0;
for(i=0;i<n;++i)
{
t=temp^arr[i];
if( t<arr[i] )
++js;
}
L 基本同K
M 裸威佐夫博弈(含本题代码)
N 阶梯博弈(逆向思维,从后往前去,注意区间端点的联系)
代码
#include<cstdio>
#include<cstring>
using namespace std;
int main()
{
int n,f[40];
while(~scanf("%d",&n))
{
f[1]=9;
for(int i=2;i<=40;i++)
f[i]=(i%2)?9*f[i-1]:2*f[i-1];
for(int i=1;i<=40;i++)
{
if(f[i]>=n)
{
printf("%s\n",(i%2)?"Stan wins.":"Ollie wins.");
break;
}
}
}
return 0;
}
必胜区间:右端点=上一个必败区间的右端点
×
×
9
必败区间:右端点=上一个必胜区间右端点
×
×
2
又一天结束了,博弈真好玩