1. 程式人生 > >hdu 1207 漢諾塔2(dp+經典漢諾塔模型)

hdu 1207 漢諾塔2(dp+經典漢諾塔模型)

漢諾塔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 題目大意:比經典漢諾塔模型多一個杆
題目分析:
首先做這個題一定要深入瞭解經典漢諾塔模型,三杆的漢諾塔有先人總結出來的公式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] );
}