汉诺塔题目总结

参考了别人的代码的总结

1.四柱汉诺塔问题和n柱汉诺塔问题
题目:http://acm.hdu.edu.cn/showproblem.php?pid=1207

参考了别人的题解:http://www.cnblogs.com/fanzhidongyzby/archive/2012/07/28/2613173.html

#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
double f[70];

void init() {
    f[1] = 1;
    f[2] = 3;

    for(int i = 3; i <= 65; i++) {
        double Max = f[i - 2] * 2 + 3; 
        for(int j = 1; j < i; j++)  
            Max = min(Max, 2 * f[i - j] + pow(2,j) - 1);
        f[i] = Max;
    }
}

int main() {
    init();
    int n;
    while(scanf("%d", &n) == 1) {
        printf("%d\n", (int)f[n]);  
    }
    return 0;
}

2.计算某个盘子被移动的次数

题目:http://acm.hdu.edu.cn/showproblem.php?pid=1995
解题思路:第n个盘子移动的步数肯定是1
移动第n个盘子到c柱之后,第n-1个盘子移到c柱子之前,第n -1个盘子移动了1次,然后不考虑第n个盘子,第n-1个盘子还需要移动1次,所以总的移动了2次。
第一次第n-1个盘子到b柱时,第n-2个盘子已经移动了2次。然后将第n-1个盘子看成第n个盘子,第n-2个盘子看成第n-1个盘子,就可以递推下去了

如果不理解的话,可以动手画画,画一下就知道了

#include<cstdio>
long long ans[65];
int main() {
    ans[1] = 1;
    for(int i = 2; i <= 60; i++)
        ans[i] = ans[i - 1] * 2;
    int test, x, y;
    scanf("%d", &test);
    while(test--) {
        scanf("%d%d", &x, &y);
        printf("%I64d\n", ans[x - y + 1]);
    }
    return 0;
}

3.无规则汉诺塔

题目:http://acm.hdu.edu.cn/showproblem.php?pid=1996
解题思路:每个盘子都有三种摆放方式,所以共有3^n种

#include<cstdio>

int main() {
    long long ans[31];
    int test, n;
    ans[1] = 3;
    for(int i = 2; i <= 30; i++)
        ans[i] = ans[i-1] * 3;
    scanf("%d", &test);
    while(test--) {
        scanf("%d", &n);
        printf("%I64d\n",ans[n]);
    }
    return 0;
}

4.状态查询

题目http://acm.hdu.edu.cn/showproblem.php?pid=1997
解题思路:按照最优汉诺塔的规则
1. 将A柱上的n-1个盘子放到B柱子上
2. 将A柱子上的第n个盘子移动到C柱子上
3. 最后将B柱子上的n-1个盘子移动到C柱子上
这三个形成递归过程,也就是说,第n个盘子所在的位置要么是在A柱子上,要么就是在C柱子上,且是将A柱上的n移动到C柱子上了
设一个函数f(int n,int * a, int *b, int *c)
代表三个柱子,如果第n个盘子在b柱子上,表示false了
如果第n个盘子在a柱子上,下一步就是要把第n-1个盘子从a柱子移动到c柱子上
如果第n个盘子在c柱子上,下一步就是要把b柱子上的第n-1个盘子移动到c柱子上了

#include<cstdio>
#include<cstring>
#define maxn 70
int num[3][maxn];
bool solve(int n, int *one, int *two, int *three) {
    if(two[0] == n)
        return 0;
    else if(one[0] == n)
        return solve(n - 1, one + 1, three, two);
    else if(three[0] == n)
        return solve(n - 1, two, one, three + 1);
    return 1;
}

int main() {
    int test, n;
    scanf("%d", &test);
    while(test--) {
        int n, m;
        scanf("%d", &n);
        for(int i = 0; i < 3; i++) {
            scanf("%d", &m);
            for(int j = 0; j < m; j++) 
                scanf("%d", &num[i][j]);
            num[i][m] = -1;
        }
        printf("%s\n", solve(n,num[0],num[1],num[2]) ? "true" :"false");
    }
    return 0;
}

5.不规则移动
A.
题目:http://acm.hdu.edu.cn/showproblem.php?pid=2064
解题思路:
1.先把A柱子上的n-1个盘子移动到C柱子上
2.再把A柱子上的第n个盘子移动到B柱子上
3.再把C柱子上的n-1个盘子移动到A柱子上(和第一步对称)
4.把B柱子上的第n个盘子移动到C柱子上
5.最后把A柱子上的n-1个盘子移动到C柱子上

#include<cstdio>

int main() {
    long long ans[40];
    int n;
    ans[1] = 2;
    for(int i = 2; i <= 35; i++)
        ans[i] = ans[i - 1] * 3 + 2;
    while(scanf("%d", &n) == 1) {
        printf("%I64d\n", ans[n]);
    }
    return 0;
}

B.
题目:http://acm.hdu.edu.cn/showproblem.php?pid=2077
解题思路:其实我的思路蛮水的,强制推出
假设有数组
f数组纪录的是将A柱上的n个移动到C柱上,或者将C柱上的n个移动到A柱上,和上一题的表示是一样的

d数组纪录的是将A柱上的n个移动到B柱上需要的步数
可得到递推公式
d[n] = f[n – 1] + 1 + f[n – 2] + 1 + d[n – 2]
表示先将A柱上的n-1个移动到C柱子上,再将第n个从A柱移动到B柱上,再将C柱子上的n-2个盘子移动到A柱子上,再将第n-1个盘子移动到B柱子上,最后再将A柱子上的n-2个盘子移动到B柱子上

e数组纪录的是将B柱子上的盘子移动到C柱子上
可得到递推公式
e[n] = e[n – 2] + 1 + f[n – 2] + 1 + f[n – 1]
先讲B柱子上的n-2个盘子移动到C柱子上,再将第n-1个盘子移动到A柱子上,再将C柱子上的n-2个盘子移动到A柱子上,再将B柱子上的第n个盘子移动到C柱子上,最后减A柱子上的n-1个盘子移动到C柱子上

g数组纪录的是符合所有规则的移动次数
得递推公式
g[n] = f[n-2] + 1 + f[n-3] + 1 + d[n-3] + 2 + e[n-3] + 1 + f[n-3] + 1 + f[n-2]
具体的自己思考了

#include<cstdio>
#include<algorithm>
using namespace std;
#define maxn 25

int main() {
    long long f[maxn], g[maxn], d[maxn], e[maxn];
    f[1] = 2;
    for(int i = 2; i <= 20; i++) 
        f[i] = 3 * f[i - 1] + 2;

    d[1] = 1, d[2] = 4;
    for(int i = 3; i <= 20; i++)
        d[i] = f[i - 1] + f[i - 2] + 2 + d[i - 2];
    e[1] = 1, e[2] = 4;     

    for(int i = 3; i <= 20; i++)
        e[i] = e[i - 2] + 2 + f[i - 2] + f[i - 1];
    g[1] = 2, g[2] = 4, g[3] = 10;
    for(int i = 4; i <= 20; i++) {
        g[i] = 2 * f[i - 2] + 2 * f[i - 3] + 6 + d[i - 3] + e[i - 3];
        g[i] = min(g[i],f[i]);
    }
    int test, n;
    scanf("%d", &test);
    while(test--) {
        scanf("%d", &n);
        printf("%lld\n", g[n]);


    }
    return 0;
}

6.计算第n步移动的是哪个盘子
http://acm.hdu.edu.cn/showproblem.php?pid=2175

解题思路:
第一个盘子在第1步的时候会被移动
只有两个盘子的情况下,第一个盘子在第一步的时候会被移动,然后在第3步的时候会被移动到,也就是在盘子2移动到C柱子的后的
只有三个盘子的情况下,前面的移动和两个盘子的移动是一样的,然后第一个盘子在第5步的时候会被移动到,然后移动的步骤和两个的情况下又是一样的
也就是说第一个盘子的移动是1,3,5,7…
第二个盘子的移动是2,6,10,14…
第三个盘子的移动是4,12,20…
以此递归的

#include<cstdio>

int main() {
    long long ans[64];
    ans[1] = 1;
    for(int i = 2; i <= 63; i++)
        ans[i] = ans[i - 1] * 2;
    long long n, m;
    while(scanf("%lld%lld", &n, &m) == 2 && n + m) {
        for(int i = 1; i <= n; i++) {
            long long t = m / ans[i];
            if(t % 2 == 1 && m % ans[i] == 0) {
                printf("%d\n", i);
                break;
            }
        }
    }
    return 0;
}

7.每根柱子的状态
A. http://acm.hdu.edu.cn/showproblem.php?pid=2184

解题思路:先纪录把n个盘子从A柱子移动到C柱子的所需要步数,如果当前的步数大于把n个盘子从A柱子移动到C柱子的所需步数,就表示可以把n个盘子移动到B柱子,然后把第n+1个盘子移动到C柱子上,就表示第n+1个盘子在C柱子上了,以此递归下去

#include<cstdio>
#include<algorithm>
using namespace std;

int main() {
    int row[3][64];
    long long ans[64], m;
    ans[1] = 1;
    for(int i = 2; i <= 63; i++)
        ans[i] = ans[i - 1] * 2 + 1;
    int *A, *B, *C, test, n;
    scanf("%d", &test);
    while(test--) {
        scanf("%d%lld", &n, &m);
        A = row[0], B = row[1], C = row[2];
        *A = 1, *B = 1, *C = 1;
        while(n--) {
            if(m <= ans[n]) {
                *(A + *A) = n + 1;  
                (*A)++;
                swap(B,C);
            }
            else {
            *(C + *C) = n + 1;
                (*C)++;
                swap(A,B);
                m -= (ans[n] + 1);
            }
        }
        for(int i = 0; i < 3; i++) {
            printf("%d", row[i][0] - 1);
            for(int j = 1; j < row[i][0]; j++)
                printf(" %d", row[i][j]);
            printf("\n");
        }
    }
    return 0;
}

B.http://acm.hdu.edu.cn/showproblem.php?pid=2511

解题思路:纪录要在第几步才能移动第n个盘子
1.如果当前步骤数等于要移动第n个盘子的步数,就表示刚好把第n个盘子从A柱子移动到C柱子
2.如果当前步骤数大于要移动第n个盘子的步数,就把第n个盘子从A柱子移动到C柱子,再继续递归
3.如果当前步骤数小于要移动第n个盘子的步数,就表示第n个盘子不用移动,接着递归,把第n-1个盘子从A柱子移动到B柱子

#include<cstdio>
long long ans[64];
void move(int n, long long m, int s, int e) {
    int mid = 6 - s - e;
    long long tmp = ans[n];
    if(m == tmp) {
        printf("%d %d %d\n", n, s, e);
        return ;
    }
    if(m > tmp) {
        move(n - 1, m - tmp, mid , e);
    }
    else
        move(n - 1, m, s, mid);
}
int main() {
    ans[1] = 1;
    for(int i = 2; i <= 63; i++)
        ans[i] = ans[i - 1] * 2;
    int n, test;
    long long m;
    scanf("%d", &test);
    while(test--) {
        scanf("%d%lld", &n, &m);
        move(n, m, 1, 3);
    }
    return 0;
}
    原文作者: 汉诺塔问题
    原文地址: https://blog.csdn.net/l123012013048/article/details/45276649
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞