前言
学习 SG 的内容在这就不赘述,因为发现了一篇特别好的文章。
博弈论是个十分有意思且十分奥秒的知识点,初学者可以先看看下面两篇文章:
有趣的组合博弈(1):取石子游戏 - 知乎 (zhihu.com)
有趣的组合博弈(2):异或 与 尼姆取石子 - 知乎 (zhihu.com)
看完上面的之后可以看看SG 函数:
有趣的组合博弈(3):SG函数 - 知乎 (zhihu.com)
对于个别地方的证明可能没有那么严谨,但是我们看看也凑合够用了。
例题
CF 2004E.Not a Nim Problem
题意:
常规的取石子游戏变形,限制就是只能取和当前堆数互质的数目。
思路:
关键点是要找出SG函数,瞪眼法很难看出有什么规律,打个表就很显而易见了:
1. sg(1) = 1
2. 若 x 为偶数,则 sg(x) = 0
3. 其他情况,设 x 的最小质因子为 p ,则 sg(x) = “p 在质数中从小到大的排位”(记作num)
证明:
1 和 2 是显然的,这里我们主要考虑 3 ,用数学归纳法证明。
假设 1 ~ x - 1 都满足上述条件,考虑 sg(x) 的值,要证 x 的后继中有 0 ~ num - 1 但是不能有num。
对于 x ,我们肯定可以取 1 个或者 x - 1 个石子,这样后继就是 sg(x - 1) 和 sg(1) ,分别对应 0 和 1。接着,肯定也可以取到剩下小于 p 个石子,即 2 ~ p - 1,那么后继就是 sg(2) , sg(3), ... , sg(p - 1),根据前面的条件都满足,这些后继肯定包含了 2 ~ num - 1 的值。综上,后继中一定有 0 ~ num - 1。
那么后继能不能有 num 呢?反证。假设有,那么意思是后继中也有某一堆石子数,满足其最小质因子为 p ,这样这堆就和原来的互质了,违背题意。
因此,sg(x) = num 。
#include<cstdio>
#include<cstring>
using namespace std;
#define N 10000007
int T,n,check[N],prime[N],p[N],num[N],cnt,tmp,ans;
void ola()
{
check[1] = 1,cnt = 0;
for (int i = 2;i < N;++ i)
{
if(!check[i]) prime[++ cnt] = i,p[i] = i;
for (int j = 1;j <= cnt;++ j)
{
if(i * prime[j] >= N) break;
check[i * prime[j]] = 1,p[i * prime[j]] = prime[j];
if(!(i % prime[j])) break;
}
}
for (int i = 1;i <= cnt;++ i) num[prime[i]] = i;
return;
}
int main()
{
ola();
scanf("%d",&T);
while (T --)
{
scanf("%d",&n),ans = 0;
for (int i = 1,x;i <= n;++ i)
{
scanf("%d",&x);
if(x == 1) tmp = 1;
else if(!(x & 1)) tmp = 0;
else tmp = num[p[x]];
ans ^= tmp;
}
if(ans) printf("Alice\n");
else printf("Bob\n");
}
return 0;
}