zoukankan      html  css  js  c++  java
  • 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
    ");
            else
                printf("false
    ");
        }
        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
    " , 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
    ");
                if(k % 3 == 1)    printf("1 3
    ");
                if(k % 3 == 2)    printf("3 2
    ");
            } else {                //顺时针
                if(k % 3 == 0)    printf("3 1
    ");
                if(k % 3 == 1)    printf("1 2
    ");
                if(k % 3 == 2)    printf("2 3
    ");
            }
        }
        return 0;
    }
    HDU2511
  • 相关阅读:
    leetcode 279. Perfect Squares
    leetcode 546. Remove Boxes
    leetcode 312. Burst Balloons
    leetcode 160. Intersection of Two Linked Lists
    leetcode 55. Jump Game
    剑指offer 滑动窗口的最大值
    剑指offer 剪绳子
    剑指offer 字符流中第一个不重复的字符
    leetcode 673. Number of Longest Increasing Subsequence
    leetcode 75. Sort Colors (荷兰三色旗问题)
  • 原文地址:https://www.cnblogs.com/H-Vking/p/4362539.html
Copyright © 2011-2022 走看看