汉诺塔II


Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 5810    Accepted Submission(s): 2834



Problem Description


经典的汉诺塔问题经常作为一个递归的经典例题存在。可能有人并不知道汉诺塔问题的典故。汉诺塔来源于印度传说的一个故事,上帝创造世界时作了三根金刚石柱子,在一根柱子上从下往上按大小顺序摞着64片黄金圆盘。上帝命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一回只能移动一个圆盘。有预言说,这件事完成时宇宙会在一瞬间闪电式毁灭。也有人相信婆罗门至今仍在一刻不停地搬动着圆盘。恩,当然这个传说并不可信,如今汉诺塔更多的是作为一个玩具存在。Gardon就收到了一个汉诺塔玩具作为生日礼物。
  Gardon是个怕麻烦的人(恩,就是爱偷懒的人),很显然将64个圆盘逐一搬动直到所有的盘子都到达第三个柱子上很困难,所以Gardon决定作个小弊,他又找来了一根一模一样的柱子,通过这个柱子来更快的把所有的盘子移到第三个柱子上。下面的问题就是:当Gardon在一次游戏中使用了N个盘子时,他需要多少次移动才能把他们都移到第三个柱子上?很显然,在没有第四个柱子时,问题的解是2^N-1,但现在有了这个柱子的帮助,又该是多少呢?


 



Input


包含多组数据,每个数据一行,是盘子的数目N(1<=N<=64)。


 



Output


对于每组数据,输出一个数,到达目标需要的最少的移动数。


 



Sample Input


1 3 12


 



Sample Output


1 5 81


 



Author


Gardon


 



Source


Gardon-DYGG Contest 2


题目大意:比经典汉诺塔模型多一个杆
题目分析:
首先做这个题一定要深入了解经典汉诺塔模型,三杆的汉诺塔有先人总结出来的公式2^n-1,但仅仅记住这个公式是远远不够的, 要对它进行理解,首先模拟汉诺塔的搬运过程,我们采取的是进行子问题化简,也就是将n个圆盘从a杆借助b杆搬到c杆,也就是将n-1个圆盘从a杆借助c杆转移到b杆,再将第n块圆盘从a杆转移到c杆.
递归的实现的代码如下:

#include<stdio.h>
 
void move(int n,char a,char b,char c)
{
    if(n==1)
        printf("\t%c->%c\n",a,c);    //当n只有1个的时候直接从a移动到c
    else
    {
        move(n-1,a,c,b);            //第n-1个要从a通过c移动到b
        printf("\t%c->%c\n",a,c);
        move(n-1,b,a,c);            //n-1个移动过来之后b变开始盘,b通过a移动到c,这边很难理解
    }
}
 
main()
{
    int n;
    printf("请输入要移动的块数:");
    scanf("%d",&n);
    move(n,'a','b','c');
}



可以利用这个程序将汉诺塔的操作过程打印出来再理解一下.


那么公式是怎么得到的呢?


我就不给出严格证明了,而只是给出自己的一个通俗的理解:


就是挪规模为1的汉诺塔要1步,挪规模为2的汉诺塔要首先将规模为1的挪到b,再将b上的借助a挪到c,所以要进行两次,也就是2倍的规模为1汉诺的塔的挪动次数,然后再加上挪动最底层的一次,就得到了结果


所以得到了通项公式....解释的可能不好,不懂的可以评论里咱们详谈...


那么有了汉诺塔的基础,我们就可以开始研究这个变种的汉诺塔了


因为多了一个杆,所以我们挪盘子的方式不仅仅是一种借助一个杆挪动的方案


而是多出一种可以借助两个杆的情况.


我们记dp(x)为借助两个杆挪动x格盘子的最少步数


动态规划嘛,一定要有初始状态和转移方程,


易得f(1) = 1 , f(2) = 3;


那么转移方程如何得到呢?


我们既然是借助两个杆,那么可以选择借助一个,也可以选择两个都借助


所以就分成了两种情况,如果是借助两个杆取搬运x个,再借助一个杆搬到n-x个,


先借助两个杆搬运后就有一个就不能再借用了,因为它顶端为最小


所以就又变成了经典汉诺塔模型,利用公式可以求取这一部分的次数,然后被占用的杆又腾空一个,然后变成两个杆转移的情况,继续转移即可部分全部转移到目标杆然后枚举x,算取最小值即可,然后用数组记录打表,方便查询


#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <cmath>
#define MAX 70

using namespace std;

typedef long long LL;

int n;
int dp[MAX];

int main ( )
{
    memset ( dp , 0x3f , sizeof ( dp ) );
    dp[1] = 1 , dp[2] = 3;
    for ( int i = 3 ; i < 65 ; i++ )
        for ( int j = 1 ; j < i ; j++ )
            if ( 2*dp[j] + pow(2.0,i-j)-1 <  dp[i] )
                dp[i] =  2*dp[j] + pow(2.0,i-j)-1;
    while ( ~scanf ( "%d" , &n ) )
        printf ( "%d\n" , dp[n] );
}