题解 P1242 【新汉诺塔】
有一句话说的好:“把大象装进冰箱,永远只要3步。”
对于汉诺塔(举个例子),想要将编号为5盘从A移到C,显然编号小于5的盘既不能在C也不能在A(不能挡路),所以它们必须在B上。
有以上一点可知道:要将K从X移动到Y上,其最少次数的操作中必须有一部分操作是为了必须将编号小于K的盘子移动到Z上
那么我们要将编号小于K的盘子从X移到Y上(经典汉诺塔),有以下几个操作:
1.将编号小于K的盘子全部移动到Z上
2.将K移动到Y上
3.将编号小于K的盘子移动到Y上
不过这道题改了一下……
很显然的是,对于给定的盘K,如果K已经在它自己的位子上了,那么不要动他(如果能)显然是最优的,而且它不会影响到编号小于K的任何操作
那么,我们用数组now[K]记录盘子K当前所处的柱子(1,2,3),用数组dest[k]记录盘子K的目标柱子(最终目标)
然后按编号从大到小枚举K,如果now[K] = dest[K],显然它就不要动了,大家当他不存在就好
可是如果我们碰到了需要编号的K盘怎么办
那么我们应该先将编号小于K的盘子移动到第三根柱子上,再将K移过去,于是编号小于K的盘子就留在第三根柱子上,而K完成了它的目标,就可以认为K不存在,继续枚举K了。
于是,当K的1的时候,大家都可以看作不存在了,于是达到了目标,天下大同
#include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;
namespace my
{
int now[112], dest[112];
int other[4][4];
int n;
void init()
{
other[1][2] = 3;
other[2][1] = 3;
other[1][3] = 2;
other[3][1] = 2;
other[2][3] = 1;
other[3][2] = 1;
}
void read(int *dest)
{
for (int i(1); i <= 3; ++i)
{
int k;
scanf("%d", &k);
for (int j(0); j != k; ++j)
{
int in;
scanf("%d", &in);
dest[in] = i;
}
}
}
int cnt;
void OldHnoi(int rank, int to)//将编号小于K的盘全部移动到柱子to上
{
if (rank <= 0) return;
if (now[rank] == to)
{
OldHnoi(rank - 1, to);
return;
}
OldHnoi(rank - 1, other[now[rank]][to]);
printf("move %d from %c to %c\n", rank, 'A' + now[rank] - 1, 'A' + to - 1);
now[rank] = to;
++cnt;
OldHnoi(rank - 1, to);
}
//将编号为rank的盘移动到to上,而编号小于rank的盘子会被移动到other[now[rank]][to]上
void move(int rank, int to)
{
if (rank <= 0) return;
if (now[rank] == to)
{
OldHnoi(rank - 1, to);
return;
}
OldHnoi(rank - 1, other[now[rank]][to]);
printf("move %d from %c to %c\n", rank, 'A' + now[rank] - 1, 'A' + to - 1);
now[rank] = to;
++cnt;
}
}
int main()
{
my::init();
scanf("%d", &my::n);
my::read(my::now);
my::read(my::dest);
for (int i(my::n); i != 0; --i)
{
if (my::now[i] != my::dest[i])
{
my::move(i, my::dest[i]);
}
}
printf("%d\n", my::cnt);
return 0;
}