新汉诺塔
Description
设有n(n<=50)个大小不等的中空圆盘,按从小到大的顺序从1到n编号。将这n个圆盘任意的迭套在三根立柱上,立柱的编号分别为A、B、C,这个状态称为初始状态。
现在要求找到一种步数最少的移动方案,使得从初始状态转变为目标状态。
移动时有如下要求:
- 一次只能移一个盘;
- 不允许把大盘移到小盘上面。
Analysis
没有头绪的一题,如果圆盘有序放置,是不是题目会简单很多?答案是肯定的。
显而易见,每次只能移动顶层的圆盘,但是此思维类似于爆搜,是不可取的。回到汉诺塔游戏的思路,将n-1个盘子转移走,把自己放到目标杆子上,把n-1个盘子放回来,所以是一个递归的思想。
这题规则有所改变,每个盘子都有自己的目标状态。只能把目标状态中底部的盘子先达到目标,再把另外n-1个盘子弄上去。而瞄准一个盘子怎么把它放上去呢?
首先肯定要把目标点的盘子拾搬,把自己头上的拾搬,最后才能把自己放上去。这显然是正确的,但是拾搬的扔到哪呢?只能扔到另一根柱子上了。
接着,我就没有彻底没有头绪了,因为任意迭套存在小的上面有大环的情况,这个拾搬的策略实在是太复杂了。先写一个初始状态一定为有序的code试试看。
懵逼,竟然AC了。原来是我题目理解错了,放置的时候一定是有序的...
那么这个题目就比较简单了,每次把需要移动的最大的移动过去,为了给他腾出地方,把比他小的盘子中占据目标位置和在他头上的都扔另一个杆子上去,产生子问题。
Code
#include <bits/stdc++.h>
const int N=51;
const char M[3]={'A','B','C'};
int pole[N],tar[N],step;
void move(int x,int s,int t){
if(t==s)return;
for(int i=x-1;i;i--)
if(pole[i]==t)move(i,t,3-s-t);
else if(pole[i]==s)move(i,s,3-s-t);
pole[x]=t;
step++;
printf("move %d from %c to %c\n",x,M[s],M[t]);
}
int main(){
freopen("test.in","r",stdin);
freopen("test.out","w",stdout);
int n;
scanf("%d",&n);
for(int i=0;i<2;i++)
for(int j=0;j<3;j++){
int num,p;
scanf("%d",&num);
for(int k=1;k<=num;k++){
scanf("%d",&p);
if(!i)pole[p]=j;
if(i)tar[p]=j;
}
}
for(int i=n;i>=1;i--)
move(i,pole[i],tar[i]);
printf("%d\n",step);
return 0;
}