汉诺塔问题

汉诺塔(又称河内塔)问题是源于印度一个益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。

由此产生了许多有趣的问题,而这些问题往往和递归递推有关。本文的问题均来自杭电。

经典汉诺塔I:
有三塔,A塔从小到大从上至下放有N个盘子,现在要搬到目标C上,小的必需放在大的上面,每次搬一个,求最小步数。
设盘子一共有n个,将最大的一个盘子放到C上前要将比它小的盘子都放到B上,然后再放回C。于是有


hdu 1207 汉诺塔II

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

同I的变化:现在多了一根柱子,一共有4根柱子。

分析:在汉诺塔I的基础上有了第四根柱子后,每一步都需找到最小的移动次数,不是简单的

#include <iostream>
#include <cstdio>
using namespace std;
typedef unsigned long long ull;
ull f[70];
const ull one=1;
int main()
{
    int n;
    f[1]=1;
    f[2]=3;
    for(int i=3;i<=64;i++){
        ull minm=2*f[1]+(one<<(i-1))-1;
        for(int j=2;j<i;j++){
            ull temp=2*f[j]+(one<<(i-j))-1;
            if(minm>temp) minm=temp;
        }
        f[i]=minm;
    }
    while(cin>>n){
        printf("%llu\n",f[n]);
    }
    return 0;
}

hdu 2064 汉诺塔III

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

同I的变化:不允许直接从最左(右)边移到最右(左)边(每次移动一定是移到中间杆或从中间移出)

分析:同最简单的汉诺塔思路类似:
1. 把前n-1个盘子移动到C  
2.把第n个盘子移到B
3.把前n-1个盘子移动到A
4.第n个盘子移到C
5.把前n-1个盘子移动到C。【完成】

于是得到递推式

import java.util.*;
public class Main {
    static long  []h=new long [40];
    public static void main(String[] args) {
         h[1]=2;
         for(int i=2;i<=35;i++){
             h[i]=3*h[i-1]+2;
         }
         int n;
         Scanner sc=new Scanner (System.in);
         while(sc.hasNext()){
             n=sc.nextInt();
             System.out.println(h[n]);
         }
    }

}

hdu 2077 汉诺塔IV

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

同I比较:允许最大的盘子放到最上面会怎么样呢?(只允许最大的放在最上面)当然最后需要的结果是盘子从小到大排在最右边。

分析:多了一个条件,最大的盘子可以放在最上面。那么这样想:
设h[i]表示就近放的方法数(盘子全部移动:A-->B或者B-->C)  f[i]表示跳跃移动(A-->C,C-->A,也即是问题的答案)可以得到式子:

import java.util.*;
public class Main {
    static long [] h=new long [25];
    static long []f=new long [25];
 public static void main(String[] args) {
  Scanner sc=new Scanner (System.in);
        h[1]=1;
        for(int i=2;i<=20;i++){
         h[i]=3*h[i-1]+1;
        }
        f[1]=2;
        f[2]=4;
        for(int i=3;i<=20;i++){
         f[i]=2+2*h[i-1];
        }
        int n,t;
        t=sc.nextInt();
        while(t>0){
         n=sc.nextInt();
         System.out.println(f[n]);
            t--;
        }
 }
}

hdu 1995 汉诺塔V

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

同汉诺塔I的变化: 告之盘子总数和盘号,计算该盘子的移动次数.

分析:背景是最原始的汉诺塔,h(n)=2h(n-1)+1  h(1)=1 进一步得到:.依次对应的是n盘,n-1盘,n-2盘……1盘

import java.util.*;
public class Main {
    public static void main(String[] args) {
         Scanner sc=new Scanner (System.in);
         long t,n,i;
         t=sc.nextLong();
         while(t>0){
             n=sc.nextLong();
             i=sc.nextLong();
             long ans=(long)1<<(n-i);
             System.out.println(ans);
             t--;
         }
    }
}

hdu 1996 汉诺塔VI

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

n个盘子的汉诺塔问题的最少移动次数是2^n-1,即在移动过程中会产生2^n个系列。由于
发生错移产生的系列就增加了,这种错误是放错了柱子,并不会把大盘放到小盘上,即各柱
子从下往上的大小仍保持如下关系 :
n=m+p+q 
a1>a2>...>am
b1>b2>...>bp
c1>c2>...>cq
计算所有会产生的系列总数.

分析:第n个盘子先放,有3种选择;第n-1个盘子再放,也有3种选择……第1个盘子放有3种选择。所以一共有3^n种结果。

import java.util.*;
public class Main {
 static long []f=new long [35];
 public static void main(String[] args) {
        f[1]=3;
        for(int i=2;i<=30;i++){
         f[i]=f[i-1]*3;
        }
     Scanner sc=new Scanner (System.in);
         int n,t;
         t=sc.nextInt();
         while(t>0){
          n=sc.nextInt();
          t--;
          System.out.println(f[n]);
         }
 }
}

hdu 1997 汉诺塔VII

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

n个盘子的汉诺塔问题的最少移动次数是2^n-1,即在移动过程中会产生2^n个系列。由于发生错移产生的系列就增加了,这种错误是放错了柱子,并不会把大盘放到小盘上,即各柱子从下往上的大小仍保持如下关系 : 
n=m+p+q
a1>a2>...>am
b1>b2>...>bp
c1>c2>...>cq
ai是A柱上的盘的盘号系列,bi是B柱上的盘的盘号系列, ci是C柱上的盘的盘号系列,最初目标是将A柱上的n个盘子移到C盘. 给出1个系列,判断它是否是在正确的移动中产生的系列.

例1:n=3
3
2
1
是正确的
例2:n=3
3
1
2
是不正确的。
注:对于例2如果目标是将A柱上的n个盘子移到B盘. 则是正确的.

分析:
递归递推思想。把n个盘子从A移动到C的最少步骤:
1)a1,a2……a(n-1)移动到B,
2)an移动到C
3)将a1,a2……a(n-1)移动到C。
完成1)把B看做C,C看做B,A不变
完成3)把B看做A,A看做B,C不变
递归进行这个操作,如果an出现在了B柱子(相对的),那么步骤就是错误的。
a1,a2……a(n-1)就是新的子问题。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int m[3],map[3][70];
int judge;
void  dfs(int sum,int a,int b,int c){
    if(judge==0 || judge==1) return ;
    if(sum==0){ judge=1; return ; }
    if(map[a][m[a]]==sum){
        m[a]++;
        dfs(sum-1,a,c,b);
    }
    else if(map[c][m[c]]==sum){
        m[c]++;
        dfs(sum-1,b,a,c);
    }
    else {
        judge=0;
        return ;
    }
}
int main()
{
    //freopen("cin.txt","r",stdin);
    int t,n;
    cin>>t;
    while(t--){
        scanf("%d",&n);
        memset(map,0,sizeof(map));
        for(int i=0;i<3;i++){
            scanf("%d",&m[i]);
            for(int j=0;j<m[i];j++){
                scanf("%d",&map[i][j]);
            }
        }
        m[0]=m[1]=m[2]=0;
        judge=-1;
        dfs(n,0,1,2);
        if(judge==1) puts("true");
        else  puts("false");
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值