数学知识
首先补充一点数学知识
SG函数
mex运算
m e x ( a 1 , a 2... a n ) 的 结 果 为 这 个 集 合 内 没 有 出 现 过 的 最 小 正 数 mex(a1,a2...an)的结果为这个集合内没有出现过的最小正数 mex(a1,a2...an)的结果为这个集合内没有出现过的最小正数
SG函数的取值
若 S G ( x ) 可 到 达 的 情 况 为 S G ( a 1 , a 2 . . . . a n ) 那 么 S G ( x ) = m e x ( a 1 , a 2 . . . . a n ) 若SG(x)可到达的情况为SG(a_{1},a_{2}....a_{n})\\ 那么SG(x)=mex(a_{1},a_{2}....a_{n}) 若SG(x)可到达的情况为SG(a1,a2....an)那么SG(x)=mex(a1,a2....an)
一 巴什博弈
1 定义
假设这里有一堆数量为n的石子,有两个人从石堆里吗取出1~m颗石子,最终没有石子可取的那个人输。
2 输赢情况
情况一:
n
=
m
+
1
先
手
取
s
(
1
~
m
)
个
,
后
者
再
取
n
−
s
个
,
先
者
必
输
\quad n=m+1 \\ 先手取s(1~m)个,后者再取n-s个,先者必输\\
n=m+1先手取s(1~m)个,后者再取n−s个,先者必输
情况二:
n
=
(
m
+
1
)
∗
r
先
手
每
次
取
s
(
1
m
)
个
,
后
手
每
次
取
n
−
s
个
,
最
终
还
是
后
手
必
赢
\quad n=(m+1)*r\\ 先手每次取s(1~m)个,后手每次取n-s个,最终还是后手必赢\\
n=(m+1)∗r先手每次取s(1 m)个,后手每次取n−s个,最终还是后手必赢
情况三:
n
=
(
m
+
1
)
∗
r
+
x
先
手
取
x
个
,
那
么
后
手
遍
置
于
情
况
二
下
,
先
手
必
赢
\quad n=(m+1)*r+x 先手取x个,那么后手遍置于情况二下,先手必赢
n=(m+1)∗r+x先手取x个,那么后手遍置于情况二下,先手必赢
3 代码实现
#include <stdio.h>
int main()
{
int n,m;
scanf("%d%d",&n,&m);
if(n%(m+1)) printf("first win");
else printf("lose");
return 0;
}
二 巴什博弈的变形
1 定义
假设这里有一堆数量为n的石子,每次只能从其中拿走限定数量的石子,求必胜局面
2 输赢情况
若能取的石子数量为1 3 4,当石子数量为0时,先手必输,当数量为1 3 4 时,后手必输,以此递推,每个局面都是非赢即输,且可以在一步之内转化,这种结果很类似于SG函数若该状态能取到必输状态,即为必赢状态。
3 代码实现
#include <stdio.h>
#include <string>
int choices[3]={1,3,4};
int vis[100];
int SG[100];
int mex(int n)
{
for (int i=0; i<=n; i++)
{
if(!vis[i])
return i;
}
return -1;
}//查找mex的值
void GetSG(int n)
{
for (int i=1; i<=n; i++)
{
memset(vis, 0, sizeof(vis));//初始化vis为0
for (int j=1; j<=3&&choices[j]<i; j++)
{
vis[SG[i-choices[j]]]=1;//将可取到的vis设为1
}
SG[i]=mex(i);
}
}
三 威佐夫博弈 ( Wythoff’s Game )
1 定义
有两堆数量分别为a,b的石子,有两个人依次从其中取出石子,有两种取石子的方法,从一堆石子中取出任意个和从两堆石子中取出同样多个。
2 输赢情况
在威佐夫博弈中存在先手必输的局面,我们将这种局面称为奇异局势,用(a , b)表示现在两堆的状况
已知(0, 0)为奇异局势,那么(k, 0), (0, k), (k, k)都为非奇异局势,据此类推我们找到后面的几种奇异局势(1, 2), (3, 5), (4, 7), (6, 10)…
据此我们进行假设
1.
a
p
为
之
前
未
出
现
过
的
最
小
正
整
数
2.
b
p
=
a
p
+
p
\begin{aligned} &1.\quad a_{p}为之前未出现过的最小正整数\\ &2.\quad b_{p}=a_{p}+p\\ \end{aligned}
1.ap为之前未出现过的最小正整数2.bp=ap+p
进行数学归纳法证明
假
设
在
之
前
的
k
∈
[
1
,
n
]
,
(
a
k
,
a
k
+
n
+
1
)
都
为
奇
异
局
势
,
我
们
只
需
证
明
(
a
n
+
1
,
a
n
+
1
+
n
+
1
)
为
奇
异
局
势
1.
从
左
边
拿
一
点
,
因
为
比
a
n
+
1
小
的
数
都
在
前
面
存
在
过
,
所
以
只
需
要
在
右
边
取
适
当
的
数
就
可
变
化
为
奇
异
局
势
2.
从
右
边
拿
一
点
,
如
果
右
边
取
的
太
多
,
那
么
转
化
为
情
况
一
,
如
果
拿
的
较
少
,
则
在
左
边
取
相
应
的
数
转
换
为
前
面
的
奇
异
局
势
3.
从
两
边
同
时
拿
一
点
,
则
可
以
直
接
取
为
前
面
的
一
个
奇
异
局
势
。
\begin{aligned} &假设在之前的k\in[1,n],(a_{k},a_{k}+n+1)都为奇异局势,\\ &我们只需证明(a_{n+1},a_{n+1}+n+1)为奇异局势\\ &1.从左边拿一点,因为比a_{n+1}小的数都在前面存在过,所以只需要在右边取适当的数就可变化为奇异局势\\ &2.从右边拿一点,如果右边取的太多,那么转化为情况一,如果拿的较少,则在左边取相应的数转换为前面的奇异局势\\ &3.从两边同时拿一点,则可以直接取为前面的一个奇异局势。 \end{aligned}
假设在之前的k∈[1,n],(ak,ak+n+1)都为奇异局势,我们只需证明(an+1,an+1+n+1)为奇异局势1.从左边拿一点,因为比an+1小的数都在前面存在过,所以只需要在右边取适当的数就可变化为奇异局势2.从右边拿一点,如果右边取的太多,那么转化为情况一,如果拿的较少,则在左边取相应的数转换为前面的奇异局势3.从两边同时拿一点,则可以直接取为前面的一个奇异局势。
并且对于每一个非奇异局势,都可在一次取数转化为一个奇异局势面对非奇异局势,先拿者必胜。
我们可以发现对于
a
n
a_{n}
an和
b
n
b_{n}
bn它们很像Beatty数列。
{
1
α
+
1
β
=
1
⌊
a
n
⌋
=
α
n
⌊
b
n
⌋
=
β
n
由
a
n
+
n
=
(
α
+
1
)
n
=
β
n
,
得
β
=
α
+
1
带
入
上
式
解
出
α
=
5
+
1
2
,
由
此
我
们
可
以
得
到
a
n
和
b
n
的
通
项
,
满
足
此
通
项
的
坐
标
\left\{ \begin{array}{ll} \frac{1}{\alpha} +\frac{1}{\beta}=1\\ \lfloor a_{n} \rfloor=\alpha n \quad\lfloor b_{n} \rfloor=\beta n\\ \end{array} \right. \\ 由a_{n}+n=(\alpha +1)n=\beta n,得\beta=\alpha +1\\带入上式解出 \alpha=\frac{\sqrt{5}+1}{2},由此我们可以得到a_{n}和b_{n}的通项,满足此通项的坐标
{α1+β1=1⌊an⌋=αn⌊bn⌋=βn由an+n=(α+1)n=βn,得β=α+1带入上式解出α=25+1,由此我们可以得到an和bn的通项,满足此通项的坐标
3 代码实现
#include <stdio.h>
#include <math.h>
double use = (sqrt(5.0)+1)/2;
int n,m;
int main()
{
scanf("%d%d",&n,&m);
if(n<m)
{
int temp=n;
n = m;
m = temp;
}
int a = n - m;
// int ans = use * a;
if(m == (int)(use * a))//如果刚好为n乘以use,则为奇异局势,先手必输
printf("0");
else
printf("1");
}
四 Nim游戏
1 定义
有n堆石子,每人每次可以从一堆石子中取出任意颗石子,无法操作的一方输
2 输赢情况
当所有堆的石子都为0的时候,那么每一堆数目的异或和都为0,在后面的任意一种状态下,每一堆的异或和都可以通过一步变化变为0,具体操作为将使得最高位为1的数单独提出,使他变为剩下数的异或和,从而达到后手必输的局面
3 代码实现
#include <stdio.h>
#include <math.h>
int main()
{
//for (int i=1; i<=10001; i++) f[i]=i;
int T;
int a[100010];
scanf("%d",&T);
while (T--)
{
int n;
scanf("%d",&n);
int ans=0;
for (int i=1; i<=n; i++)
{
scanf("%d",&a[i]);
ans^=a[i];
}
if (ans) printf("Yes\n");
else printf("No\n");
}
return 0;
}