题目描述
一个经典问题改编自本·艾姆斯·威廉姆斯的短篇小说《椰子》。故事讲述五个人和一只猴子遭遇海难,他们在岛上度过第一个夜晚收集椰子。晚上,一个人醒来,决定取走他的那份。他把椰子分成五堆,剩下一个给了猴子,然后藏起自己的那份继续睡觉。随后第二个人醒来,做了同样的事情:将椰子分成五堆,剩一个给猴子,藏起一份。第三、第四、第五个人也依次照做。第二天早上,他们醒来后将剩下的椰子分成五等份,这次没有剩余。
问最初收集了多少椰子?最小的答案是 312131213121。但本题不是求这个。
现在反过来:如果知道最初收集的椰子数,问最多能有多少个人(和一只猴子)参与这个过程,使得上述过程能够发生?
输入格式
输入包含一系列整数,每行一个,表示收集的椰子总数。输入以负数结束。
输出格式
对于每个椰子数,输出最大可能的人数,格式为:
X coconuts, Y people and 1 monkey
如果没有可行解,则输出:
X coconuts, no solution
样例
输入
25
30
3121
-1
输出
25 coconuts, 3 people and 1 monkey
30 coconuts, no solution
3121 coconuts, 5 people and 1 monkey
题目分析
设椰子总数为 NNN,人数为 ppp(p≥2p \ge 2p≥2)。晚上有 ppp 个人依次醒来,每次操作如下:
- 当前椰子数为 xxx。
- 此人将 xxx 分成 ppp 堆,若 x mod p=1x \bmod p = 1xmodp=1,则余下 111 个给猴子,然后取走其中一堆(即拿走 x−1p\frac{x-1}{p}px−1 个),剩下的椰子数为:
x′=x−1−x−1p=(x−1)⋅p−1p x' = x - 1 - \frac{x-1}{p} = (x-1) \cdot \frac{p-1}{p} x′=x−1−px−1=(x−1)⋅pp−1 - 若 x mod p≠1x \bmod p \neq 1xmodp=1,则过程无法继续。
共进行 ppp 次这样的操作(ppp 个人各一次)。最后第二天早上,将剩下的椰子分成 ppp 等份,要求刚好分完,即最后剩下的椰子数 yyy 满足 y mod p=0y \bmod p = 0ymodp=0。
我们需要找到最大的 ppp,使得上述递推过程能够完整执行。
观察第一次操作:初始 NNN 必须满足 N mod p=1N \bmod p = 1Nmodp=1,即 N−1N-1N−1 能被 ppp 整除。因此 ppp 必定是 N−1N-1N−1 的一个因子(p>1p > 1p>1)。于是我们可以枚举 N−1N-1N−1 的所有因子,从大到小尝试,找到第一个满足完整过程的 ppp,即为最大人数。
模拟过程时,对于候选 ppp,按照递推式逐步计算 ppp 次,每次检查是否满足 x mod p=1x \bmod p = 1xmodp=1,若满足则更新 xxx,否则失败。完成 ppp 步后,检查 x mod px \bmod pxmodp 是否为 000。
解题思路
-
读入椰子数 NNN,若 N<0N < 0N<0 则结束。
-
特殊情况:
- 若 N≤2N \le 2N≤2:没有可行解(因为至少需要 222 人)。
- 若 N=3N = 3N=3:只有 222 人可行(第一个人醒来,拿走一个给猴子,剩下两个,分成两份,一份一个,拿走一个,还剩下一个;第二个人醒来,拿走一个给猴子,剩下零个,分成两份,都是零个,拿走零个,剩下零个;第二天醒来,剩下零个椰子,可以平分,符合题目要求)。
-
一般情况:令 M=N−1M = N - 1M=N−1,求 MMM 的所有因子(从 222 开始)。将所有因子按降序排列,依次尝试。
-
对于每个候选 ppp,模拟 ppp 次操作:
- 令 x=Nx = Nx=N。
- 循环 ppp 次:
- 检查 (x−1) mod p(x - 1) \bmod p(x−1)modp 是否为 000,若不是则失败。
- 更新 x=(x−1)⋅(p−1)/px = (x - 1) \cdot (p - 1) / px=(x−1)⋅(p−1)/p。
- 完成 ppp 次后,检查 x mod px \bmod pxmodp 是否为 000,若是则 ppp 可行。
-
第一个可行的 ppp 即为最大人数,输出;若无则输出无解。
复杂度分析
- 求 N−1N-1N−1 的因子:O(N)O(\sqrt{N})O(N)。
- 每个因子模拟 ppp 次操作,总次数不超过因子个数 ×\times× 最大因子,但因子个数远小于 N\sqrt{N}N,且 ppp 也远小于 NNN。总体时间复杂度可以接受。
- 空间复杂度 O(N)O(\sqrt{N})O(N)。
代码实现
// Coconuts Revisited
// UVa ID: 616
// Verdict: Accepted
// Submission Date: 2016-08-28
// UVa Run Time: 0.070s
//
// 版权所有(C)2016,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
int main() {
cin.tie(0); cout.tie(0); ios::sync_with_stdio(false);
int n, nn;
while (cin >> n, n >= 0) {
nn = n;
if (n <= 2) {
cout << n << " coconuts, no solution\n";
continue;
}
if (n == 3) {
cout << "3 coconuts, 2 people and 1 monkey\n";
continue;
}
n--;
vector<int> bigger, smaller;
int up_limit = sqrt(n);
for (int i = 2; i <= up_limit; i++)
if (n % i == 0) {
bigger.push_back(n / i);
smaller.insert(smaller.begin(), i);
}
for (int i = 0; i < smaller.size(); i++) bigger.push_back(smaller[i]);
bool solutionFound = false;
for (int i = 0; i < bigger.size(); i++) {
int times = 0, people = bigger[i];
n = nn;
while (true) {
n--;
if (n % people == 0) n = (n / people) * (people - 1);
else break;
times++;
if (n % people == 0 || times > people) break;
}
if (times == people && n % people == 0) {
cout << nn << " coconuts, " << people << " people and 1 monkey\n";
solutionFound = true;
break;
}
}
if (!solutionFound) cout << nn << " coconuts, no solution\n";
}
return 0;
}
总结
本题通过对椰子数进行逆向模拟,利用第一次操作必须满足 N−1N-1N−1 能被人数整除这一关键条件,从而将人数限制在 N−1N-1N−1 的因子范围内。枚举所有因子并模拟完整过程,即可求得最大可行人数。该解法简洁高效,关键在于理解递推关系和枚举范围的缩小。注意特殊情况 N=3N=3N=3 和 N≤2N \le 2N≤2 的处理。
1511

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



