ZOJ Problem Set - 2338 The Towers of Hanoi Revisited DFS+预处理

ZOJ Problem Set - 2338 The Towers of Hanoi Revisited

题目描述:

  题目链接:Problem Set - 2338 The Towers of Hanoi Revisited

题目大意:

  给定 N1<=N<=64 个盘子和 M4<=M<=65 根柱子,问把 N 个盘子从1号柱子移动到 M 号柱子所需要的最少步数,并且输出移动过程。

解题思路:

  对于整个移动的过程可以概括为如下:

  • 先把k个盘子,通过j跟柱子移动到中间的某根柱子上,步数: f[k][j] ;

    • 把剩下的 ik 个盘子通过剩下的 j1 跟柱子移到第j跟柱上,步数: f[ik][j1]

    • 最后再把中间的 k 个盘子移到J柱上,步数:f[k][j]

        为防止重复计算,且该题状态有限 6465 ,因此作为预处理将所有的状态所需最小步数求出。
      再通过 DFS 按照上述思路打印出移动步骤即可。

    • 复杂度分析:

      时间复杂度 O(n2)
      空间复杂度 O(n2)

      AC代码:

      #include <cstdio>
      #include <stack>
      #include <cstring>
      #include <algorithm>
      #include <iostream>
      using namespace std;
      
      const int maxn = 70;
      const unsigned long long INF=0xffffffffffffffff;//定义无限大
      unsigned long long f[maxn][maxn]; //f[i][j]表示i个盘,通过j个柱子,全程移动所需要的最少次数。
      int pre[maxn][maxn]; //pre[i][j]表示f[i][j]的最优方案是先将最小的path[i][j]个盘移动到中间某根的柱子上
      int n,m;//n个盘子 m个柱子
      stack<int>stk[maxn];//每个柱子看做一个栈
      bool used[maxn];//用来标记当前柱子是否为空
      
      void init(){  //初始化,预处理所有盘数与柱数的组合下,需要移动的次数
          memset(f,INF,sizeof(f));
          for(int i = 3; i <= 65; i++){
              f[0][i] = 0;
              f[1][i] = 1;
          }//所有盘数为0的情况,步数为0.所有盘数为1的情况,步数为1
          for(int i = 1; i <= 64; i++){
              f[i][3] = f[i-1][3] * 2 + 1;
              pre[i][3] = i - 1;
          }//初始化三根柱子的情况
          for(int i = 2; i <= 64; i++){//盘数从2起
              for(int j = 4; j <= 65; j++){ //柱数从3起
                  for(int k = 1; k < i; k++){//为达到最优方案处于中间柱上的盘数。
                      if(f[i][j] > f[i-k][j-1] + 2*f[k][j]){
                          f[i][j] = f[i-k][j-1] + 2*f[k][j];
                          pre[i][j] = k;
                      }
          /*先把k个盘子,通过j跟柱子移动到中间的某根柱子上f[k][j];
            把剩下的i-k个盘子通过剩下的j-1跟柱子移到第j跟柱子f[i-k][j-1];
            最后再把中间的k个盘子移到J柱上f[k][j]。
            共计f[i-k][j-1] + 2*f[k][j]*/
                  }
              }
          }
      }
      
      void dfs(int nt,int mt,int src,int des){//nt盘数,mt柱数,src起点,des终点
          if(nt == 1){
              if(stk[des].size())
                  printf("move %d from %d to %d atop %d\n",stk[src].top(),src,des,stk[des].top());
              else
                  printf("move %d from %d to %d\n",stk[src].top(),src,des);
              stk[des].push(stk[src].top());
              stk[src].pop();
              return;
          }
          int peg = 0; //柱子编号
          for(int i = 1; i <= m; i++){
              if(i != src && i != des && !used[i] ){
                  peg = i;
                  break;
              }
          }
          dfs(pre[nt][mt],mt,src,peg);//先把k个盘子,通过j跟柱子移动到中间的某根柱子上
          used[peg] = true;
          dfs(nt - pre[nt][mt],mt-1,src,des);//把剩下的i-k个盘子通过剩下的j-1跟柱子移到第j跟柱子
          used[peg] = false;
          dfs(pre[nt][mt],mt,peg,des);//最后再把中间的k个盘子移到J柱上
      }
      
      int main(){
          init();
          int N;
          cin >> N;
          while(N--){
              scanf("%d%d",& n,& m);
              for(int i = 1; i <= m; i++)while(!stk[i].empty()) stk[i].pop();//清空栈
              for(int i = n; i >= 1; i--)stk[1].push(i); //给第一根柱子上放盘
              memset(used,false,sizeof(used));
              printf("%llu\n",f[n][m]);
              dfs(n,m,1,m);
          }
          return 0;
      }
      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值