大家已经很熟悉三根柱子的汉诺塔问题。扩展到一般情况,如果一共有m(3 <= m <= 40)根柱子,将n (1 <= n <= 30) 个大小不同的盘子从一根柱子移动到另一根柱子上,且始终保证大盘不能放在小盘上面,问最少需要多少步?
两个数,m和n,中间用空格隔开,分别代表柱子的数量和盘子的个数。
仅一个数字,为最小的步数
3 3
7
这道题是三柱汉诺塔问题的变形,若还没有懂三柱汉诺塔问题解法的读者#戳此#。
我目前假定读者已经可以熟练运用三柱汉诺塔问题的解法,并可以通过构造二叉树的方法以及遍历的方法证明完成汉诺塔的步数为2n−1。
那我们先从比较小的方面分析吧,分析m=4,这个时候依然是借用中转柱移动最大盘子之前的所有盘子,但我们现在有不同的移法,将盘子根据从小到大以1,2,……,n标记,从左到右柱子的编号为A,B,C,D,当1到n-1个盘子被移动过之后,有这么两种可能。
(1)1到j个盘子到B柱上(不失一般性,B柱和C柱在这里是等价的)(其中1≤j≤n),j+1到n-1个盘子在C柱上,第n个盘子在A柱上。
(2)1到n-1个盘子在B柱上,第n个盘子在A柱上。
那我们到底哪种最优呢?如果第一种最优,那么j应该取什么值呢?
注意提问方式,“最优”和“取值”。
最优的取用,采用动态规划。不知道取哪个,那就直接穷举吧,反正也只是O(n)的日常操作。
那我们既然要取最小步数,那肯定有以下的状态转移方程:
f[i][j]=min(f[i][j],f[i−1][k]+2∗f[i][j−k])(1≤k≤j)
这里的k表示最优的取值,其中最优表示对于1方案来说最小的步数。
那这里我们运用了前人所证明的多柱汉诺塔的一般步骤:
(1)使用N柱汉诺塔算法将m−k个盘子移动到B柱上,通过其他的空柱。
(2)使用N−1柱汉诺塔算法将剩余的k个盘子移动到Z柱上(Z为最右边的柱)
(3)使用N柱汉诺塔算法将m−k个盘子从B柱移动到Z柱上。
这种算法我们称为Frame算法
好吧你应该知道的,我一向不爱证明。
所以我在这里略去所有关于Frame算法的证明(其实应该也不用证,这应该是一个很自然的想法)。
可惜这种算法不可以打印出解决多柱汉诺塔问题的步骤,是一件比较遗憾的事。
好了,懒人们来抄代码吧:
#include
#include
long long step[45][35]; //m n
long long min(long long a, long long b)
{
return a < b ? a : b;
}
int solve(int m, int n)
{
for(int i = 0; i < 45; i++)
for(int j = 0; j < 35; j++)
step[i][j] = 9223372036854775807;
/*既然要取最小值,这里初始化取极大值就好了,因为我们假定不可能有45柱以内的汉诺塔解题步骤大于long long的最大值*/
for(int i = 1; i <= n; i++)
//m=3的基本子问题
step[3][i] = (long long)(pow(2, i)) - 1;
for(int i = 1; i <= m; i++)
{
/*任何柱的0盘子,1盘子,2盘子的步数都是固定的,是基本子问题*/
step[i][0] = 0;
step[i][1] = 1;
step[i][2] = 3;
}
for(int i = 4; i <= m; i++)
{
for(int j = 3; j <= n; j++)
{
for(int k = 1; k <= j; k++)
step[i][j] = min(step[i][j], 2 * step[i][j - k] + step[i - 1][k]);//状态转移方程
}
}
}
int main()
{
int m, n;
scanf("%d%d", &m, &n);
solve(m, n);
printf("%lld\n", step[m][n]);
}
/*代码版权归属:叶茂青,中山大学智能科学与技术专业2017级新生*/
原题链接如下:(应该只有中大IP可以打开)
http://matrix.sysu.edu.cn/course/90/assignment/3670——多柱汉诺塔问题(动态规划)
P.S.只有Standard Test的这道题是真的水,打表秒过,都没有那么多想法。
2017.12.11
KarK.Li