题解【 [USACO22DEC] Circular Barn S】

先考虑 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(kN)

现在将问题还原,对于某一格的数,如果是必败点,那么先手会尽量拖延时间,也就是说选择数使其能够经过尽可能多的轮数;否则就会用尽可能少的轮数结束来赢得游戏。现在进行分类讨论:

  • aia_iai111 或质数,显然一轮就能结束;

  • aia_iai 为奇数,从第一个不大于它的质数(或 111,下同)开始判断,找到尽可能大的质数符合质数加上必败点的形式;

  • aia_iai 为偶数,无论是必胜还是必败点,每次都只会取 222。下面给出证明:

    1. aia_iai 为必败点,先手会尽量拖延时间,取的数尽可能小,不难发现取 222 会比取 111 优。
    2. 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;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值