HDU汉诺塔系列

  这几天刷了杭电的汉诺塔一套,来写写题解。

  HDU1207 汉诺塔II

  HDU1995 汉诺塔V

  HDU1996 汉诺塔VI

  HDU1997 汉诺塔VII

  HDU2064 汉诺塔III

  HDU2077 汉诺塔IV

  HDU2175 汉诺塔IX

  HDU2184 汉诺塔VIII

  HDU2511 汉诺塔 X

 


 

HDU1207 汉诺塔II

  题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1207 ,多柱汉诺塔问题。

先说下四柱汉诺塔的Frame算法: 

  (1)用4柱汉诺塔算法把A柱上部分的n- r个碟子通过C柱和D柱移到B柱上。  【F( n- r )步】

  (2)用3柱汉诺塔经典算法把A柱上剩余的r个碟子通过C柱移到D柱上。  【2^r-1步】

  (3)用4柱汉诺塔算法把B柱上的n-r个碟子通过A柱和C柱移到D柱上。 【F(n-r)步】

  (4)依据上边规则求出所有r(1≤r≤n)情况下步数f(n),取最小值得最终解。

    因此Frame算法的递归方程如下:  F(n)=min(2*F(n-r)+2^r-1),(1≤r≤n)。

  通过这个方程我们能得到所有4柱汉诺塔的步骤个数,同时也有人证明了,对于四柱汉诺塔,当r=(sqrt(8*n+1)-1)/2时,能保证f(n)取得最小值。所以算法的复杂度是F(n)=O(sqrt(2*n)*2^ sqrt(2*n))。

基于四柱汉诺塔的Frame算法,我们可以引申到多柱(M柱)汉诺塔的情况,我们简称M柱汉诺塔算法:

  (1)用M柱汉诺塔算法把1柱上部分的n-r个碟子通过3…M柱移到2柱上。  【M( n- r )步】

  (2)用M-1柱汉诺塔算法把1柱上剩余的r个碟子通过3…M-1柱移到M柱上。  【<M-1>(r)步】

  (3)用M柱汉诺塔算法把2柱上的n-r个碟子通过1柱和3…M柱移到M柱上。  【M( n- r )步】

  (4)依据上边规则求出所有r(1≤r≤n)情况下步数m(n),取最小值得最终解M(n)。

 

综上,题目的解答可以通过方程直接得到

#include <iostream>
#include <cstdio>
#include <cmath>
#include <queue>
#include <vector>
#include <string>
#include <string.h>
#include <algorithm>
using namespace std;
#define LL __int64
const int maxn = 65;
LL pow2[maxn] , r[maxn] , tower4[maxn];
int main() 
{
    int n;
    pow2[0] = 1;
    for(int i = 1 ; i < maxn ; i++)
        pow2[i] = pow2[i - 1] << 1;
    for(int i = 1 ; i < maxn ; i++) {
        double tmp = (sqrt(8.0 * i + 1) - 1) * 0.5;
        r[i] = int(tmp);
    }
    for(int i = 1 ; i < maxn ; i++) {
        int j = r[i];
        tower4[i] = 2 * tower4[i - j] + pow2[j] - 1;
    }
    while(cin >> n)
    {
        cout << tower4[n] << endl;
    }
    return 0;
}
HDU1207

 


 

HDU1995 汉诺塔V

  题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1995 ,简单题。

先贴一些关于汉诺塔的结论:

  一号柱有n个盘子,叫做源柱.移往3号柱,叫做目的柱.2号柱叫做中间柱.
  全部移往3号柱要f(n) =(2^n)- 1次.
  最大盘n号盘在整个移动过程中只移动一次,n-1号移动2次,i号盘移动2^(n-i)次.
  1号盘移动次数最多,每2次移动一次.
  第2k+1次移动的是1号盘,且是第k+1次移动1号盘.
  第4k+2次移动的是2号盘,且是第k+1次移动2号盘.
  ......   

  第(2^s)k+2^(s-1)移动的是s号盘,这时s号盘已被移动了k+1次.

  每2^s次就有一次是移动s号盘.
  第一次移动s号盘是在第2^(s-1)次.
  第二次移动s号盘是在第2^s+2^(s-1)次.
  ......
  第k+1次移动s号盘是在第k*2^s+2^(s-1)次.
  1--2--3--1叫做顺时针方向,1--3--2--1叫做逆时针方向.
  最大盘n号盘只移动一次:1--3,它是逆时针移动.
  n-1移动2次:1--2--3,是顺时针移动.
  如果n和k奇偶性相同,则s号盘按逆时针移动,否则顺时针.

 

所以根据上面的知识,可知k号盘移动 2^(n - k)次

#include <iostream>
#include <cstdio>
#include <cmath>
#include <queue>
#include <vector>
#include <string>
#include <string.h>
#include <algorithm>
using namespace std;
#define LL __int64
const int maxn = 60;
LL pow2[maxn];
int main() 
{
    int n , k , T;
    pow2[0] = 1;
    for(int i = 1 ; i <= maxn ; i++)
        pow2[i] = pow2[i - 1] << 1;
    cin >> T;
    while(T--)
    {
        cin >> n >> k;
        cout << pow2[n - k] << endl;
    }
    return 0;
}
HDU1995

 


 

HDU1996 汉诺塔VI

  题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1996 ,水题。

  因为每个盘子有三个选择,所以就是3^n。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <queue>
#include <vector>
#include <string>
#include <string.h>
#include <algorithm>
using namespace std;
#define LL __int64
const int maxn = 29;
LL pow3[maxn];
int main() 
{
    int n , k , T;
    pow3[0] = 1;
    for(int i = 1 ; i <= maxn ; i++)
        pow3[i] = pow3[i - 1] * 3;
    cin >> T;
    while(T--)
    {
        cin >> n;
        cout << pow3[n] << endl;
    }
    return 0;
}
HDU1996

 


 

 HDU1997 汉诺塔VII

  题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1997 ,递归判断。

算法:

  由于传统汉诺塔一共有三步:

  (1)先把n-1个盘子从A盘借助C盘移到B盘;

  (2)把最大盘n号盘从A盘移动到C盘;

  (3)把n-1个盘子从B盘借助A盘移动到C盘。

所以此题如下分析:

  * 首先判断是否都在A盘或者都在C盘,如果是这种情况就说明都没放或者都已经放好了,这时候返回true;

  * 这时候开始考虑最大盘n号盘,根据上面可知n号盘只能在A柱或C柱上,在B柱上则返回false(因为B为中间柱);

  * 如果n号盘在A上,则说明这时在进行(1)步,这时减小规模,考虑n-1号盘,移动方向为A->B(C为中间柱);

  * 如果n号盘在C上,则说明此时在进行(3)步,这时减小规模,考虑n-1号盘,移动方向为B->C(A为中间柱);

  * 按照上述过程递归,n为0时候返回true即可。

 

#include <iostream>
#include <cstdio>
#include <cmath>
#include <queue>
#include <vector>
#include <string>
#include <string.h>
#include <algorithm>
using namespace std;
#define LL __int64
const int maxn = 70;
int a[maxn] , b[maxn] , c[maxn];
bool check(int n , int a[] , int b[] , int c[])
{
    if(n == 0)
        return true;
    for(int i = 1 ; b[i] != 0 ; i++) {
        if(b[i] == n)
            return false;
    }
    for(int i = 1 ; a[i] != 0 ; i++) {
        if(a[i] == n)
            return check(n - 1 , a , c , b);
    }
    for(int i = 1 ; c[i] != 0 ; i++) {
        if(c[i] == n)
            return check(n - 1 , b , a , c);
    }
}
int main() 
{
    int n , m , p , q , T;
    cin >> T;
    while(T--)
    {
        memset(a , 0 , sizeof(a));
        memset(b , 0 , sizeof(b));
        memset(c , 0 , sizeof(c));
        scanf("%d" , &n);

        scanf("%d" , &m);
        for(int i = 1 ; i <= m ; i++)
            scanf("%d" , &a[i]);
        scanf("%d" , &p);
        for(int i = 1 ; i <= p ; i++)
            scanf("%d" , &b[i]);
        scanf("%d" , &q);
        for(int i = 1 ; i <= q ; i++)
            scanf("%d" , &c[i]);

        if(m == n || q == n || check(n , a , b , c))
            printf("true\n");
        else
            printf("false\n");
    }
    return 0;
}
HDU1997

 


 

HDU2064 汉诺塔III

  题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2064 ,公式递推。

算法:

  由于题目的限制,所以应该这样移动:

  (1)先把n-1个盘子从A柱通过B柱移动到C柱;

  (2)把最大盘第n号盘从A移动到B;

  (3)把前n-1个盘子从C柱通过B柱移动到A柱;

  (4)把最大盘第n号盘从B移动到C;

  (5)把前n-1个盘子从A通过B移动到C。

  根据上述,可以得到递推公式为:f(n) = 3 * f(n - 1) + 2 , 即 f(n) = 3 ^ n - 1

 

#include <iostream>
#include <cstdio>
#include <cmath>
#include <queue>
#include <vector>
#include <string>
#include <string.h>
#include <algorithm>
using namespace std;
#define LL __int64
const int maxn = 36;
LL pow3[maxn];
int main() 
{
    pow3[0] = 1;
    for(int i = 1 ; i < maxn ; i++)
        pow3[i] = pow3[i - 1] * 3;
    int n;
    while(cin >> n)
        cout << pow3[n] - 1 << endl;
    return 0;
}
HDU2064

 


 

HDU2077 汉诺塔IV

   题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2077 ,递推。

算法:

  这道题其实是前一道加了一些变换,移动步骤:

  (1)先把n-2个盘子从A柱通过B柱移动到C柱;

  (2)把 n 和 n-1 号盘从A移动到B;

  (3)把前n-2个盘子从C柱通过B柱移动到A柱;

  (4)把 n 和 n-1 号盘从B移动到C;

  (5)把前n-2个盘子从A通过B移动到C。

  设n个盘子的结果为p(n),可得到递推公式:  p(n) = 3 * f(n - 2) + 4 , 即p(n) = 3 ^ (n - 1) + 1 (注:这里的f(n)指的是HDU2064的结果)

 

#include <iostream>
#include <cstdio>
#include <cmath>
#include <queue>
#include <vector>
#include <string>
#include <string.h>
#include <algorithm>
using namespace std;
#define LL __int64
const int maxn = 36;
LL pow3[maxn];
int main() 
{
    pow3[0] = 1;
    for(int i = 1 ; i < maxn ; i++)
        pow3[i] = pow3[i - 1] * 3;
    int n , T;
    cin >> T;
    while(T--) {
        cin >> n;
        cout << pow3[n - 1] + 1 << endl;
    }
    return 0;
}
HDU2077

 


 

 HDU2175 汉诺塔IX

  题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2175 ,简单题。

算法:

  根据上面的结论:第 k+1 次移动s号盘是在第 k*2^s+2^(s-1) 次

  所以如果有满足 m % (2 ^ s) == 2 ^ (s-1)即说明这次移动的是s号盘。

 

#include <iostream>
#include <cstdio>
#include <cmath>
#include <queue>
#include <vector>
#include <string>
#include <string.h>
#include <algorithm>
using namespace std;
#define LL __int64
const int maxn = 64;
int main() 
{
    int n , k;
    LL m , s , t;
    while(~scanf("%d %I64d" , &n , &m) && n && m)
    {
        s = 1;
        t = s << 1;
        for(k = 1 ; k <= n ; k++) {
            if(m % t == s)
                break;
            s = t;
            t <<= 1;
        }
        printf("%d\n" , k);
    }
    return 0;
}
HDU2175

 


 

HDU2184 汉诺塔VIII

   题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2184 , 递归。

算法:

  还是先说传统汉诺塔的三步:

  (1)先把n-1个盘子从A盘借助C盘移到B盘;

  (2)把最大盘n号盘从A盘移动到C盘;

  (3)把n-1个盘子从B盘借助A盘移动到C盘。

  根据上题的那个结论:第 k+1 次移动s号盘是在第 k*2^s+2^(s-1) 次,所以n号盘如果移动的话,说明m >= 2 ^ (n-1)。所以对第n号来说有下面两种情况:

  * 如果m >= 2 ^ (n - 1),说明这时最大号盘已经移动,即n号盘此时在C上,此时正在进行的是(3),这时减小规模为n-1,m -= 2 ^ (n-1);

  * 如果m < 2 ^ (n - 1),说明这时最大号盘还未移动,n号盘此时在A上,此时正在进行的是(1),这时减小规模为n-1,m不变;

  递归调用该过程,n == 0返回。

 

#include <iostream>
#include <cstdio>
#include <vector>
#include <queue>
#include <cmath>
#include <string>
#include <string.h>
#include <algorithm>
using namespace std;
#define LL __int64
#define eps 1e-8
#define INF 1e8
const int maxn = 63;
LL pow2[maxn];
vector <int> a , b , c;
void move(int n , LL m , vector <int> &a , vector <int> &b , vector <int> &c)
{
    if(n == 0)
        return;
    if(m >= pow2[n - 1]) {
        c.push_back(n);
        m -= pow2[n - 1];
        move(n - 1 , m , b , a , c);
    } else {
        a.push_back(n);
        move(n - 1 , m , a , c , b);
    }
}
void print(vector <int> a)
{
    printf("%d" , a.size());
    if(!a.empty()) {
        for(int i = 0 ; i < a.size() ; i++)
            printf(" %d" , a[i]);
    }
    puts("");
}
int main()
{
    int n , T;
    LL m;
    pow2[0] = 1;
    for(int i = 1 ; i < maxn ; i++)
        pow2[i] = pow2[i - 1] << 1;
    cin >> T;
    while(T--) 
    {
        scanf("%d %I64d" , &n , &m);
        a.clear();
        b.clear();
        c.clear();
        move(n , m , a , b , c);
        print(a);
        print(b);
        print(c);
    }
    return 0;
}
HDU2184

 


 

 HDU2511 汉诺塔 X

  题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2511 ,根据结论来。

算法:

  第k+1次移动s号盘是在第k*2^s+2^(s-1)次。

  1--2--3--1叫做顺时针方向,1--3--2--1叫做逆时针方向。

  最大盘n号盘只移动一次:1--3,它是逆时针移动。

  n-1移动2次:1--2--3,是顺时针移动。

  如果n和k奇偶性相同,则s号盘按逆时针移动,否则顺时针。

  所以如果 m == k*2^s+2^(s-1),说明此时是第 k+1 次移动s号盘,这时再根据k+1和n的奇偶性判断移动方向。

 

#include <iostream>
#include <cstdio>
#include <cmath>
#include <queue>
#include <vector>
#include <string>
#include <string.h>
#include <algorithm>
using namespace std;
#define LL __int64
const int maxn = 64;
int main() 
{
    int n , T;
    LL m;
    cin >> T;
    while(T--)
    {
        scanf("%d %I64d" , &n , &m);
        LL s , t , l , k;
        s = 1;    t = s << 1;            
        for(l = 1 ; l <= n ; l++) {
            if(m % t == s)    break;    //满足 k*(2^l) + 2^(l-1) == m
            s = t;                //s 和 t分别表示 2^(l-1)、2^l
            t <<= 1;
        }
        printf("%d " , l);        //此时移动的是l号盘
        k = m / t + 1;            //第k次移动l号盘
        if(n % 2 == l % 2) {    //逆时针
            if(k % 3 == 0)    printf("2 1\n");
            if(k % 3 == 1)    printf("1 3\n");
            if(k % 3 == 2)    printf("3 2\n");
        } else {                //顺时针
            if(k % 3 == 0)    printf("3 1\n");
            if(k % 3 == 1)    printf("1 2\n");
            if(k % 3 == 2)    printf("2 3\n");
        }
    }
    return 0;
}
HDU2511

 

posted on 2015-03-24 13:41  Vking不说话  阅读(1079)  评论(0编辑  收藏  举报

导航