本题要求生成 Recamán 数列的前 n 项,并将这些项从小到大排序后输出。数列的生成规则如下:
我们需要按照这个规则计算出前 n 项,然后排序输出。题目保证 n≤3000,时间限制为 1000 ms,内存限制为 256 MB。
本题可以分为两个独立的步骤:
生成数列
按照规则逐项计算。我们需要快速判断一个数是否已经在数列中出现过。本题中 n≤3000,数列中的数值不会太大(参考代码中假设最大值不超过 106),因此可以直接使用一个布尔数组 vis 记录某个数是否出现过,这样查找的时间复杂度为 O(1)。
从 a1=1 开始,标记 vis[1] = true。对于 i 从 2 到 n,计算 ai−1−i,如果该值大于 0 且未被标记,则 ai 取该值;否则取 ai−1+i。最后将 ai 标记为已出现。
排序输出
生成完数列后,对数组的前 n 个元素进行排序。由于 n≤3000,即便使用 O(n2) 的冒泡排序也足以在时限内完成(30002=9×106 次比较和交换完全可接受)。当然,可以直接使用标准库中的 sort 函数将复杂度降为 O(nlogn),但参考代码中给出了一个手写的冒泡排序实现,这里会一并讲解。
下面结合 C++ 参考代码详细说明每一步。
cpp1#include <cstdio> 2#include <algorithm> 3using namespace std; 4const int N = 2e5 + 5; 5const int C = 1e6 + 5; 6int n; 7int a[N]; 8int vis[C]; 9 10void bubble_sort(int *a, int n) { 11 bool flag = true; 12 while (flag) { 13 flag = false; 14 for (int i = 1; i < n; ++i) { 15 if (a[i] > a[i + 1]) { 16 flag = true; 17 int t = a[i]; 18 a[i] = a[i + 1]; 19 a[i + 1] = t; 20 } 21 } 22 } 23} 24 25int main() { 26 scanf("%d", &n); 27 a[1] = 1; 28 vis[1] = 1; 29 for (int i = 2; i <= n; i++) { 30 if (a[i - 1] - i <= 0 || vis[a[i - 1] - i]) 31 a[i] = a[i - 1] + i; 32 else 33 a[i] = a[i - 1] - i; 34 vis[a[i]] = 1; 35 } 36 bubble_sort(a, n); 37 for (int i = 1; i <= n; i++) 38 printf("%d%c", a[i], " \n"[i == n]); 39 return 0; 40}
a[N]:存储数列,N = 2e5 + 5,实际只需要 n+5 的大小,但开大一些更安全。vis[C]:标记某个数字是否出现过,C = 1e6 + 5。数列在 n=3000 时产生的最大值不会超过 1e6(实际数值远小于该上限),因此数组足够容纳。a[1] = 1,vis[1] = 1;i 从 2 到 n:
a[i-1] - i <= 0:如果该值不是正整数(≤0),必须执行加法;vis[a[i-1] - i] 为真(已经出现过),同样执行加法;a[i] 在 vis 数组中标记为出现。这一过程直接模拟了题目规则,每步操作都是 O(1) 的。
参考代码提供了一个冒泡排序的实现 bubble_sort:
flag 表示本轮是否发生了交换;flag = true;因为 n≤3000,冒泡排序的最坏时间复杂度为 O(n2),在本题中完全可行。
printf("%d%c", a[i], " \n"[i == n]); 巧妙地控制了分隔符:不为最后一个元素时输出空格,最后一个元素输出换行。
如果使用标准 sort 函数,时间复杂度可优化为 O(nlogn),但本题数据规模不需要。
虽然参考代码的冒泡排序足以通过,但为了养成良好的编程习惯,推荐使用 C++ STL 中的 sort 替代手写冒泡排序:
cpp1sort(a + 1, a + 1 + n);
这样仅需一行即可完成排序,且时间复杂度更优。完整简化版代码就不再赘述,读者可以自行替换。
本题考察了对数列递推规则的理解和模拟能力,以及基础的排序操作。难点在于正确判断减法条件(正整数且未出现过)并使用高效的方法记录状态。对于 n≤3000 的数据规模,即使使用 O(n2) 排序也能轻松通过。希望读者通过本题进一步熟悉数组标记法和排序算法的应用。