一. 程序题(共1题,100分)
1. (程序题, 100分)
汉诺塔是一个古老的益智玩具。有三根柱子a, b, c,在柱子a上从上往下按照小大顺序摞着n片圆盘(编号从1到n)。现在要把圆盘从上面开始按小大顺序重新摆放在柱子c上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
输入盘子的数量n, 和 步骤 k。 求n个盘子的汉诺塔,第k步骤时三根柱子上从小到大的圆盘编号。
输入:
每行代表一个例子,两个整数n和k(中间隔一个空格),直至0和0结束(0和0不算例子)
k确保是中间步骤,n<50
输出:
每个例子一行,n或n+1或n+2个整数,依次是abc柱子上的圆盘编号(圆盘之间输出一个0,没有圆盘什么也不输出),每个柱子上的变化按从大到小排,数之间(包括0)有一个字符‘-’(键盘上减号),最后数字后面也有‘-’。最后一个例子也有回车
输入例子:
5 1
3 2
0 0
输出例子:
5-4-3-2-0-0-1-
3-0-2-0-1-
思路及代码:
汉诺塔问题很容易想到递归解决,事实上通过递归也很容易写出一个对应的解题程序。但是值得注意的是,n<50,k理论上可达到2的49次方量级,符合题目中的K很大(老师善意的提醒,hhh),递归就不适宜了。所以,可以通过数学规律的方法了解决问题(2的n次方,大概率是有规律的)。通过先前编写的递归代码,我们很容易知道,圆盘是如下顺序移动的。
1->2->1->3->1->2->1->4->1->2->1->3->1->2->1->5->1->2->1->3->1->2->1->4
通过观察,我们很容易得到圆盘的移动规律,第i块圆盘是在第2的i-1次方步开始移动的,此后每隔2的i次方步移动一次,在三柱上循环移动。那么就可以得到k步以后,圆盘到底在哪个柱上。因此只需做n次计算即可解决问题,代码如下。
#include<bits/stdc++.h>
# define ll long long
using namespace std;
ll get_ind(ll num, ll t, ll z) {
//设a, b, c三柱分别对应0, 1, 2三个下标
if (num % 2 && z % 2) {
//当n为奇数时,奇数块移动的规律是a->c->b->a,所以需要调整
if (t == 2)t = 1;
else if (t == 1)t = 2;
} else if (num % 2 == 0 && z % 2 == 0) {
// 同理,当n为偶数时,偶数块也需要调整
if (t == 2)t = 1;
else if (t == 1)t = 2;
}
return t; //返回统一的下标
}
int main() {
ll n, k, tmp, cnt, ind;
int a[3][55]; // a[0]表示a柱, a[1]表示b柱, a[2]表示c柱
while (cin >> n >> k) {
if (n == 0 && k == 0)break;
memset(a, 0, sizeof(a)); //1代表存在, 0代表不存在, a[1][2]=0
for (int i = 1; i <= n; i++)a[0][i] = 1; // 初始所有圆盘都在a上, 所以置1
for (ll i = 1; i <= n; i++) {
// 通过观察移动情况可以发现圆盘的移动规律
tmp = (ll) pow(2, i - 1); //第i号圆盘第一次移动是在第2的i-1次方步
if (k < tmp)break; //k小于第一次移动的步数,说明没移动,后续圆盘也不可能移动
cnt = ((k - tmp) / (tmp * 2) + 1) % 3; //i圆盘后续每隔2的i次方步移动一次,加上第一次移动的步数,且在三柱上循环移动
ind = get_ind(i, cnt, n); //奇数块和偶数块的移动方向不一致,需要统一下标
swap(a[0][i], a[ind][i]); //交换数值,也就是将圆盘移动到对应的柱上
}
for (int j = 0; j <= 2; j++) {
for (int i = n; i > 0; i--) //从大到小输出
if (a[j][i])cout << i << "-";
if (j < 2)cout << 0 << '-';
}
cout << endl;
}
return 0;
}
其中,奇数盘和偶数盘的移动策略是不一致的(如a-b-c-a和a-c-b-a),所以他们的除余步数0,1,2的含义存在不同,需要额外处理。
Tips:以上思路及样例代码仍存在改进部分,仅供参考。