UVa10795新汉诺塔-深入了解递归


题目链接:点击这里

汉诺塔问题回顾

递推思路

首先我们想想最开始学递归时的汉诺塔问题。首先,我们并不是细致到每一步去看到底应该如何移动,而是把前面 n1 n − 1 个盘子看成整体,这样,我们只需把前面 n1 n − 1 个盘子移动到 B B ,最大的盘子移动到C,然后把剩下的 n1 n − 1 个盘子移动到 C C 即可。
其实很多递归的问题的解决思路都是这样,不需要你去把整个步骤都想清楚,只要想一个递推式就可。因此,我们可以很容易的写出汉诺塔问题的代码:

    void move(int count,int start,int finish,int temp)
    {
        if (count > 0){
            move(count -1,start,temp,finish);
            cout<<"Move disk"<<cout<<"from"<<<start<<"to"<<finish<<endl;
            move(count-1,temp,finish,start);
        }
    }

深入理解

我们注意到,在解决汉诺塔问题时,最大的盘子始终只被移动一次,这也是证明此种方法就是最优解的条件。我们可以设当有n个盘子时最小移动步数为 F(n) F ( n ) ,现在我们想得到一个关于 n n 的递推公式。
1. 在把n个盘子从 A A 移动到C的过程中,必然存在一步,是把最大的盘子从 A A 拿出来。要想把最大的盘子从A移动到别的某个柱子上( B B C),就必须保证剩下的 n1 n − 1 个盘子 C C B上。要保证 n1 n − 1 个盘子都在剩下那个柱子上,至少需要 F(n1) F ( n − 1 ) 次移动。
2. 在将最大的盘子放到目标柱子上后,可以证明,最优解的情况下,最大的盘子不再移动。因此,需要将剩下的 n1 n − 1 个盘子放到目标柱子上,至少需要 F(n1) F ( n − 1 ) 次移动。
3. 由于最大的盘子至少需要移动一次,因此,总花费为 F(n)=2×F(n1)+1 F ( n ) = 2 × F ( n − 1 ) + 1

很容易得到显式解 2n11 2 n − 1 − 1

新汉诺塔问题

题目详解

这个题目与传统的汉诺塔问题很相似,但是其初始条件和终止条件都发生了变化,不再是单一的状态。由之前的分析可得,其最关键的思路是确保最大盘只移动一次。而迁移到本题,我们可以想到:

从编号最大的盘开始数,如果其起始状态和终止状态都相同,则最优解中不需要移动这个盘。

我们可以用反证法很容易得到这个证明。因此,我们首先根据初始状态和终止状态,找出其状态不同的最大编号,记为 k k ,那么k必须移动。
现在又回到最初的思路,可以想象:当移动 k k 的一瞬间,假设k需要从 A A 移动到B,比 k k 大的盘子不需要移动且不会影响我们的移动,可以当成不存在。比k小的盘子既不能在 A A 上,也不能在B上,因此只能在 C C 上。因此,当我们要移动k时,状态必须只能是: A A 上只有k,在 B B 上为空,其余盘子按照顺序在C
由于盘子的移动是可逆的,因此我们可以分别求初始状态和终止状态到当前状态所需的最小步数之和即可。用函数表示就是

Func(start,k1,6start[k]finish[k])+Func(finish,k1,6start[k]finish[k])+1 F u n c ( s t a r t , k − 1 , 6 − s t a r t [ k ] − f i n i s h [ k ] ) + F u n c ( f i n i s h , k − 1 , 6 − s t a r t [ k ] − f i n i s h [ k ] ) + 1

其中, 6start[k]finish[k] 6 − s t a r t [ k ] − f i n i s h [ k ] 就是我们需要将 k1 k − 1 个盘子移动到的柱子(1或者2或者3)。
如何计算 Func(P,i,final) F u n c ( P , i , f i n a l ) 呢?根据之前的推理,如果 P[i]=final P [ i ] = f i n a l ,则不需要移动,即为 Func(P,i,final)=Func(P,i1,final) F u n c ( P , i , f i n a l ) = F u n c ( P , i − 1 , f i n a l ) ;否则就要前 i1 i − 1 个盘子移动到 6P[i]final 6 − P [ i ] − f i n a l 做中转,然后将第 i i 个盘子移动后,再将已经排好序的i1个盘子放入目标柱子。注意,根据之前经典汉诺塔问题的求解,最后一个步骤需要 2i11+1=2i1 2 i − 1 − 1 + 1 = 2 i − 1 步。由此可得, Func(P,i,final)=Func(P,i1,6P[i]final)+2n1 F u n c ( P , i , f i n a l ) = F u n c ( P , i − 1 , 6 − P [ i ] − f i n a l ) + 2 n − 1

代码示例

注意,此题需要使用long long类型输出才可AC。

#include <iostream>

using namespace std;

long long recursive(int p[],int i,int target)
{
    if (i == 0) return 0;
    if (p[i] == target)
        return recursive(p, i-1 ,target);
    return recursive(p, i-1 , 6 - p[i] - target) + (1LL<<(i-1));
}

const int maxn = 60+5;

int main()
{
    int start[maxn],finish[maxn],N;
    int kcase = 1;
    while (cin>>N && N)
    {
        for (int i = 1; i <= N; i++)
        {
            cin>>start[i];
        }
        for (int i = 1; i <= N; i++)
        {
            cin>>finish[i];
        }
        int k = N;
        long long  ans = 0;
        while (k >= 1 && start[k] == finish[k])
        {k--;
        }
        if (k >=1)
        {
            int other = 6 - start[k] - finish[k];
            ans = recursive(start,k-1,other) + recursive(finish,k-1,other) + 1;
        }
        cout<<"Case "<<kcase++<<": "<<ans<<endl;
    }
    return 0;
}

欢迎关注我的个人博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值