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
  • 相关阅读:
    php 处理 json_encode 中文显示问题
    php输出cvs文件,下载cvs文件
    php服务器端生成csv文件
    在VS2013中强制IIS Express应用程序池使用经典模式
    align=absMiddle属性设置
    30个惊人的插件来扩展 Twitter Bootstrap
    jquery.fullCalendar官方文档翻译(一款小巧好用的日程管理日历, 可集成Google Calendar)
    jquery操作select(取值,设置选中)
    Bootstrap Paginator 分页 demo.
    uniform 中checkbox通过jquery 选中
  • 原文地址:https://www.cnblogs.com/H-Vking/p/4362539.html
Copyright © 2011-2022 走看看