NCU-算法分析与设计-Hanoi塔的第K步(K很大)

一. 程序题(共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:以上思路及样例代码仍存在改进部分,仅供参考。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值