题解:洛谷 P5727 冰雹猜想
【题目来源】
【题目描述】
给出一个正整数 \(n\),然后对这个数字一直进行下面的操作:如果这个数字是奇数,那么将其乘 \(3\) 再加 \(1\),否则除以 \(2\)。经过若干次循环后,最终都会回到 \(1\)。经过验证很大的数字 \((7\times 10^{11})\) 都可以按照这样的方式比变成 \(1\),所以被称为“冰雹猜想”。例如当 \(n\) 是 \(20\),变化的过程是 \(20→10→5→16→8→4→2→1\)。
根据给定的数字,验证这个猜想,并从最后的 1 开始,倒序输出整个变化序列。
【输入】
输入一个正整数 \(n\)。
【输出】
输出若干个由空格隔开的正整数,表示从最后的 \(1\) 开始倒序的变化数列。
【输入样例】
20
【输出样例】
1 2 4 8 16 5 10 20
【核心思想】
-
问题分析:给定一个正整数 \(n\),按照冰雹猜想的规则(奇数则 \(n = 3n + 1\),偶数则 \(n = n / 2\))不断变化,直到 \(n\) 变为 \(1\)。需要倒序输出整个变化过程中产生的所有数字(从 \(1\) 开始到 \(n\))。本质是一个序列生成与反向输出问题,关键在于模拟过程并存储完整序列。
-
算法选择:
- 数组存储 + 倒序输出:使用数组按顺序存储从初始 \(n\) 到 \(1\) 的整个变化序列,然后从最后一个元素(即 \(1\))开始向前遍历输出。这种方法简单直接,时间复杂度 \(O(\text{序列长度})\),空间复杂度 \(O(\text{序列长度})\)。由于序列长度通常较小(对于 \(n \le 10^5\),长度约在几十到几百之间),数组大小设为 5000 足够安全。
-
关键步骤:
- 读入与初始化:读取 \(n\),将 \(n\) 存入数组
a[1],并设置索引mark = 2。 - 生成序列:使用
while (n != 1)循环,根据 \(n\) 的奇偶性计算下一个值,存入数组,并更新索引。 - 倒序输出:从
mark - 1到1遍历数组,输出每个数字,数字之间用空格分隔。
- 读入与初始化:读取 \(n\),将 \(n\) 存入数组
-
时间/空间复杂度:
- 时间复杂度:\(O(L)\),其中 \(L\) 是冰雹序列的长度,对于给定的数据范围,\(L\) 很小。
- 空间复杂度:\(O(L)\),用于存储序列。
-
模拟与猜想验证:
- 操作模拟:严格按照题目规则生成序列,没有复杂的优化或数学证明需求。
- 倒序输出:因为要求从 \(1\) 开始倒序输出,所以必须存储整个序列,然后反向遍历。
- 数组大小:由于 \(n\) 在变化过程中可能会暂时变得很大(但题目给定的 \(n\) 通常不大),数组容量 5000 足以应对,如需更安全可使用
vector动态存储。 - 适用场景:这类“模拟过程并反向输出”的问题在入门编程中很常见,核心是掌握数组存储和反向遍历的技巧。
【算法标签】
入门 #模拟
【代码详解】
#include <bits/stdc++.h> // 包含标准库头文件(万能头文件)
using namespace std; // 使用标准命名空间
/**
* 主函数 - 程序入口
* @return 程序执行状态码(0表示成功)
*/
int main()
{
int n; // 定义变量:初始数字
int a[5000]; // 定义数组:存储Collatz序列
int mark = 1; // 定义变量:数组索引标记,初始为1
cin >> n; // 输入初始数字
a[mark] = n; // 将初始数字存入数组
mark++; // 数组索引后移
// 生成Collatz序列直到n变为1
while (n != 1) // 当n不等于1时继续循环
{
if (n % 2 == 0) // 如果n是偶数
n /= 2; // n除以2
else // 如果n是奇数
n = n * 3 + 1; // n乘以3加1
a[mark] = n; // 将当前数字存入数组
mark++; // 数组索引后移
}
// 倒序输出Collatz序列
for (int i = mark - 1; i >= 1; i--)
{
cout << a[i] << " "; // 输出当前数字
}
return 0; // 程序正常结束
}
【运行结果】
20
1 2 4 8 16 5 10 20
浙公网安备 33010602011771号