这几天刷了杭电的汉诺塔一套,来写写题解。
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)。
综上,题目的解答可以通过方程直接得到
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#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; }
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)次
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#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; }
HDU1996 汉诺塔VI
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1996 ,水题。
因为每个盘子有三个选择,所以就是3^n。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#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; }
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即可。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#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; }
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
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#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; }
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的结果)
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#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; }
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号盘。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#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; }
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返回。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#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; }
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的奇偶性判断移动方向。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#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; }