先考虑 N=1N = 1N=1 的情况。若先手胜,我们称 aia_iai 为必胜点,否则为必败点。显然 1,2,31,2,31,2,3 均为必胜点;而 4=1+3=2+24 = 1 + 3 = 2 + 24=1+3=2+2,无论如何都是后手胜,所以为必败点。如果这时候你不知道如何去分析,可以尝试打出必败点的表(猜测必败点并不多),对于一个 aia_iai 若无法拆分成质数或 111 与另一个比它小的必败点的形式,那么这个 aia_iai 便是必败点。
//x 为当前需要判断的数 p[1] = p[2] = p[3] = p[5] = p[7] = 1,以此类推
ok = 0;
for (auto i : lose)
if (p[x - i]) ok = 1;//可以拆分
if (!ok) lose.push_back (i)//说明是必败点
通过打表发现必败点都是 444 的倍数。其实这很好理解,由于 444 为最小的必败点,那么对于 (4,8)(4,8)(4,8) 的数,都可以通过 4+1,4+2,4+34 + 1,4 + 2,4 + 34+1,4+2,4+3 来表示,而 888 只能表示成 444 加上一个质数,显然不存在。那么以此类推,可知必败点为 4k(k∈N∗)4k(k \in N^*)4k(k∈N∗)。
现在将问题还原,对于某一格的数,如果是必败点,那么先手会尽量拖延时间,也就是说选择数使其能够经过尽可能多的轮数;否则就会用尽可能少的轮数结束来赢得游戏。现在进行分类讨论:
-
aia_iai 为 111 或质数,显然一轮就能结束;
-
aia_iai 为奇数,从第一个不大于它的质数(或 111,下同)开始判断,找到尽可能大的质数符合质数加上必败点的形式;
-
aia_iai 为偶数,无论是必胜还是必败点,每次都只会取 222。下面给出证明:
- aia_iai 为必败点,先手会尽量拖延时间,取的数尽可能小,不难发现取 222 会比取 111 优。
- aia_iai 为必胜点,由于必败点为偶数,所以只能取 222 这一个唯一的偶质数。
至此,取数的策略已经讲解完毕。所以只要找到若干轮后最先结束的 aia_iai,判断其是必胜点还是必败点即可。代码如下:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#define init(x) memset (x,0,sizeof (x))
#define ll long long
#define ull unsigned long long
#define INF 0x3f3f3f3f
using namespace std;
const int MAX = 1e5 + 5,M = 5e6 + 500;
const int MOD = 1e9 + 7;
inline int read ();
int t,n,ok,times,cnt,a[MAX],vis[M + 5],p[MAX];
int main ()
{
//freopen (".in","r",stdin);
//freopen (".out","w",stdout);
for (int i = 2;i <= M;++i)
{
if (!vis[i]) p[++cnt] = i;
for (int j = 1;j <= cnt;++j)
{
if (i * p[j] > M) break;
vis[i * p[j]] = 1;
if (i % p[j] == 0) break;
}
}
p[0] = 1;
t = read ();
while (t--)
{
n = read ();
ok = 1;times = INF;
for (int i = 1;i <= n;++i) a[i] = read ();
for (int i = 1;i <= n;++i)
{
if (a[i] % 2 == 0 && times > a[i] / 4) ok = (a[i] % 4 != 0),times = a[i] / 4;
if (a[i] & 1)
{
int x = upper_bound (p,p + 1 + cnt,a[i]) - p - 1;
while (~x)
{
if ((a[i] - p[x]) % 4 == 0)
{
if (times > (a[i] - p[x]) / 4) ok = 1,times = (a[i] - p[x]) / 4;
break;
}
--x;
}
}
}
puts (ok ? "Farmer John" : "Farmer Nhoj");
}
return 0;
}
inline int read ()
{
int s = 0;int f = 1;
char ch = getchar ();
while ((ch < '0' || ch > '9') && ch != EOF)
{
if (ch == '-') f = -1;
ch = getchar ();
}
while (ch >= '0' && ch <= '9')
{
s = s * 10 + ch - '0';
ch = getchar ();
}
return s * f;
}
815

被折叠的 条评论
为什么被折叠?



