目录
多层汉诺塔
【题目描述】汉诺塔是一个有意思的游戏,每个柱子上套上多个中心有洞的圆盘,每次只能移动一个圆盘,并且每个圆盘不能放在比它面积小的圆盘的上面。现在有三套圆盘并叠加放在一个柱子上了,请移动圆盘,使每个柱子上的圆盘都按照相同的顺序从大到小的摆放好,也就是把三份盘子平均分开。请问对于n个不同数量的圆盘(也就是共有3*n个盘子),分别在每个柱子上分好n个盘子,最少需要移动多少步?示意图如下图。
【输入格式】
输入共1行,包括一个正整数n
【输出格式】输出共1行,一个整数,表示需要移动圆盘的最少的步骤数,
【样例输入】
1
【样例输出】
2
解析:使用数学解析法,经典的汉络塔递推
在传统的汉诺塔问题中,我们有3个位置(A,B,C),从A移动 N个圆盘到C的步数是 2^N-1。对于这个变种汉诺塔问题,我们有三套圆盘并叠加地放在一个柱子上,我们可以考虑将这个问题转化为三次传统汉诺塔问题。三套圆盘的初始堆叠状态可以类比成有三个大小相等的"巨大圆盘"。我们的目标是将这三个“巨大圆盘“平均分散到三个柱子上。对于每个“巨大圆盘”,我们要执行传统的汉诺塔移动。那么解决这个问题的步骤如下:
将最底下的“巨大圆盘"(实际上包含了 n 个最大的圆盘)移动到柱子 C。
将中间的“巨大圆盘"(中等大小的 n 个圆盘)移动到柱子 B。
将最上面的“巨大圆盘"(n 个最小的圆盘)移动到柱子 C,放在第一步中的最大圆盘上。
再次将柱子 B(中等大小的圆盘)的圆盘移动到柱子 A。
最后,将柱子C(最小的圆盘)的圆盘移动到柱子 B。每次移动一个“巨大圆盘”(即一套 n 个圆盘)的步数都是 2^n-1,因为我们实际上在执行传统汉诺塔移动。
假设 M(n)是移动 n 个不同大小的圆盘需要的步数,那么我们需要的总步数是: M(n)+ M(n)+ M(n)+M(n)+M(n)+M(n),也就是3x(2^n-1)。
以提供的样例输入为例,n=1,每次移动一个“巨大圆盘"需要 2^1-1=1步,总共需要 3x1=3步。不过要注意,题目的意思可能是,我们最终想要每个柱子上都有三个正序排列的盘子。 如果是这个理解,那么我们执行最后一步移动的时候,实际上只需要移动两个最小的盘子(不需要移动最后一个,因为它已经在目的地了),所以是2^1-1=1 加上2^1-1=1,最后一步只移动1个盘子。
对于输入的 1,实际上需要的步数是 2 而不是 3,因为当把最上面的一个"巨大圆盘"(也就是一个盘子)移动到柱子C的时候,它已经在正确的位置上了,我们不需要再次移动。
#include <bits/stdc++.h>
using namespace std;
//计算移动 n 个不同大小的圆盘到不同柱子的最少步数
unsigned long long minimumMoves(int n) {// 由于每次处理一个巨大圆盘需要 2^n-1 步
// 总共需要 3n 个圆盘,也就是 3 个巨大圆盘,所以这里需要计算 3 次
// 最后一次移动我们移动 2个最小的圆盘,并在目的地保持第一个圆盘
//因此,最后一次移动减少一步
return 3* (pow(2, n)- 1)-(1);
}
int main() {
int n;
cin >> n;
cout << minimumMoves(n) << endl;
return 0;
}
汉诺塔问题有一个经典的递归解法。对于有n个盘子的问题,可以分解为:将上面的 n-1 个盘子从起始柱子A移动到辅助柱子 B。将最大的盘子(第n个盘子)从起始柱子A移动到目标柱子C。将那 n-1个盘子从辅助柱子B移动到目标柱子C。在这种情况下,递归的解决方案中的基本步骤数是 2^n-1。
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
long long steps = 2;
for (int i = 2; i <= n; i++) {
steps=2*steps+pow(2,i)+1;
}
cout << steps << endl;
return 0;
}
过河问题
【题目描述】有n个人要渡河,但只有一条小船,这条小船一次只能坐下最多两个人,并且只有一副船桨。每个人划船的速度不一样,如果两个人一起上船,由于重量变大,划船的速度基本上相当于是划船速度最慢的那个人速度。假设给出每个人单独划船过河所花费的时间T,请问所有人都过河的总时间最短的时间?
【输入格式】输入两行,第一行是一个整数,表示要过河的几个人。第二行,是n个整数,按速度从快到慢排序好的每个人划船过河的时间。
【输出格式】输出一行,给出所有人过河所花费最短的时间。
【样例输入1】(测试数据不包含本样例)
3
1 2 3
【样例输出1】6
【样例输入2】(测试数据不包含本样例)
4
1 2 5 10
#include <bits/stdc++.h>
using namespace std;
//模拟算法
// 主函数
int main() {
int n, sum = 0; // 定义人数 `n`,以及用于存储总时间的 `sum` 初始化为 0
cin >> n; // 输入人数
vector<int> v(n, 0); // 创建一个整数向量 `v` 用于存储每个人的过河时间
for (int i = 0; i < n; i++) { // 循环输入每个人的过河时间
cin >> v[i];
}
sort(v.begin(), v.end()); // 对过河时间进行升序排序
int i;
for (i = n - 1; i > 2; i -= 2) {
// 如果次快的人和最快的人一起过河再最快的人回来的时间,大于最快和次快的人分别两次过河的时间
if (v[i - 1] + v[0] > v[1] * 2) {
sum += v[1] * 2 + v[0] + v[i]; // 按照最快和次快的人分别两次过河的方式计算总时间
}else {
sum += v[i - 1] + v[0] * 2 + v[i]; // 按照次快的人和最快的人一起过河再最快的人回来的方式计算总时间
}
}
// 处理剩余人数为 2、1、0 的情况
if (i == 2) {
sum += v[2] + v[0] + v[1]; // 三个人过河,最快和次快过去,最快回来,最快