题解:洛谷 P8901 Circular Barn
【题目来源】
洛谷:P8901 [USACO22DEC] Circular Barn S - 洛谷
【题目描述】
Farmer John 和他的死对头 Farmer Nhoj 在一个环形牛棚内玩游戏。牛棚内有 \(N(1 \le N \le 10^5)\) 个房间,第 \(i\) 个房间初始时内有 \(a_i\) 头奶牛 \((1 \le a_i \le 5 \times 10^6)\)。游戏玩法如下:
- 两位农夫将总是在同一个房间里。进入一个房间后,每位农夫各执行一次行动,Farmer John 在先。两位农夫在游戏开始时进入房间 \(1\)。
- 如果当前房间中的奶牛数量为零,则此时执行行动的农夫即失败。否则,执行行动的农夫选择一个整数 \(P\),其中 \(P\) 为 \(1\) 或一个不超过当前房间中奶牛的数量的质数,并从当前房间中移除 \(P\) 头奶牛。
- 两位农夫均完成行动之后,两位农夫移动到环形牛棚的下一间房间。也就是说,如果农夫们在房间 \(i\),那么他们会移动到房间 \(i+1\),除非他们在房间 \(N\),在这种情况下他们会移动到房间 \(1\)。
当两位农夫均采用最优策略时,求获胜的农夫。
【输入】
输入包含 \(T\) 个子测试用例。输入的第一行包含 \(T(1 \le T \le 1000)\)。下面是 \(T\) 个子测试用例。
每个子测试用例的第一行包含 \(N\),第二行包含 \(a_1, \cdots ,a_N\)。
输入保证所有 \(N\) 之和不超过 \(2 \times 10^5\)。
【输出】
对于每一个子测试用例,输出获胜的农夫,为 Farmer John 或 Farmer Nhoj 之一。
【输入样例】
5
1
4
1
9
2
2 3
2
7 10
3
4 9 4
【输出样例】
Farmer Nhoj
Farmer John
Farmer John
Farmer John
Farmer Nhoj
【核心思想】
-
问题分析:\(N\) 个环形房间,每房间 \(a_i\) 头奶牛。两位农夫轮流从当前房间移除 \(1\) 或一个质数头奶牛,然后移动到下一房间。当房间为空时行动的农夫失败。求最优策略下的获胜者。本质上是博弈论 + SG 函数分析问题:每个房间独立构成一个 Nim-like 博弈,通过分析每房间的"轮数"(即双方各行动一次的周期)确定全局胜负。
-
算法选择:
- 欧拉筛预处理:预处理 \(1\) 到 \(5 \times 10^6\) 的所有质数
- 轮数计算:对每个房间 \(a_i\),计算在双方最优策略下能进行的轮数 \(Round[i]\)
- 最小轮数决定胜负:轮数最少的房间决定胜负,若该房间轮数为奇数则 John 胜,偶数则 Nhoj 胜
-
关键步骤:
- 预处理质数:欧拉筛法求 \(1\) 到 \(maxA\) 的所有质数
- 处理每个测试用例:
- 读入 \(N\) 和 \(a[1..N]\)
- 对每个房间计算 \(Round[i]\):
- \(a_i = 1\):\(Round[i] = 1\)
- \(a_i \bmod 4 = 0\):\(Round[i] = a_i / 4 + 1\)
- \(a_i \bmod 4 = 2\):\(Round[i] = a_i / 4 + 1\)
- \(a_i \bmod 4 = 1\) 或 \(3\):找最大质数 \(p \leq a_i\) 使得 \(a_i - p\) 是 \(4\) 的倍数,\(Round[i] = (a_i - p) / 4 + 1\)
- 找 \(Round\) 最小的房间 \(minRoom\)
- 若 \(a[minRoom] \bmod 4 \neq 0\),输出
Farmer John;否则输出Farmer Nhoj
-
时间/空间复杂度:
- 时间复杂度:\(O(maxA + T \cdot N \cdot \sqrt{a_i})\),预处理 \(O(maxA)\),每个房间找质数 \(O(\sqrt{a_i})\)
- 空间复杂度:\(O(maxA)\),质数表和标记数组
-
博弈论 + 质数分析的核心思想:
- 轮数模型:每轮双方各行动一次,房间奶牛数减少 \(4\) 的倍数(因为双方各取 \(1\) 或质数,最优策略下总和趋向 \(4\))
- 模 \(4\) 分类:\(a_i \bmod 4\) 的结果决定博弈结构,\(0\) 和 \(2\) 可直接计算轮数,\(1\) 和 \(3\) 需找合适的质数调整
- 最小轮数原理:环形游戏中,双方会尽量将对手引向轮数最少的房间,该房间成为决胜点
- 奇偶性判定:轮数为奇数意味着 John 在该房间执行最后一次非零行动,Nhoj 面对空房间失败
- 适用于组合博弈、质数性质、环形策略类问题
【解题思路】

【算法标签】
普及+ #博弈论
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int maxA = 5e6 + 5; // 预计算质数的最大值
int prime[maxA], cnt; // 存储质数的数组和计数器
bool notPrime[maxA]; // 标记是否为非质数
/**
* 欧拉筛法,获取1到maxA之间的所有质数
*/
void getPrime()
{
notPrime[1] = true; // 1不是质数
for (int x = 2; x < maxA; x++)
{
if (!notPrime[x]) // 如果x是质数
{
prime[cnt++] = x; // 将x存入质数数组
}
// 筛去x的倍数
for (int j = 0; j < cnt && x * prime[j] < maxA; j++)
{
notPrime[x * prime[j]] = true; // 标记为合数
if (x % prime[j] == 0) break; // 保证每个数只被最小质因子筛去
}
}
}
const int maxN = 1e5 + 5; // 最大房间数
int a[maxN]; // 存储每个房间的初始干草捆数
int Round[maxN]; // 存储每个房间的轮数
int main()
{
getPrime(); // 预处理质数表
int t; // 测试用例数
cin >> t;
while (t--)
{
int n; // 当前测试用例的房间数
cin >> n;
// 处理每个房间
for (int i = 1; i <= n; i++)
{
cin >> a[i]; // 输入干草捆数
// 特殊情况处理
if (a[i] == 1) // 只有1捆干草
{
Round[i] = 1; // 只需1轮
}
else if (a[i] % 4 == 0) // 干草数是4的倍数
{
Round[i] = a[i] / 4 + 1; // 计算轮数
}
else if (a[i] % 4 == 2) // 干草数除4余2
{
Round[i] = a[i] / 4 + 1; // 计算轮数
}
else // 其他情况(干草数除4余1或3)
{
int target = 0;
// 寻找最大的质数,使得a[i]-target是质数
while (notPrime[a[i] - target])
{
target += 4; // 每次增加4
}
Round[i] = target / 4 + 1; // 计算轮数
}
}
// 找出轮数最少的房间
int minRound = maxA, minRoom = -1;
for (int i = 1; i <= n; i++)
{
if (Round[i] < minRound)
{
minRound = Round[i];
minRoom = i;
}
}
// 根据轮数判断赢家
cout << (a[minRoom] % 4 ? "Farmer John" : "Farmer Nhoj") << endl;
}
return 0;
}
【运行结果】
5
1
4
Farmer Nhoj
1
9
Farmer John
2
2 3
Farmer John
2
7 10
Farmer John
3
4 9 4
Farmer Nhoj
浙公网安备 33010602011771号