题目链接:点击这里。
汉诺塔问题回顾
递推思路
首先我们想想最开始学递归时的汉诺塔问题。首先,我们并不是细致到每一步去看到底应该如何移动,而是把前面
n−1
n
−
1
个盘子看成整体,这样,我们只需把前面
n−1
n
−
1
个盘子移动到
B
B
,最大的盘子移动到,然后把剩下的
n−1
n
−
1
个盘子移动到
C
C
即可。
其实很多递归的问题的解决思路都是这样,不需要你去把整个步骤都想清楚,只要想一个递推式就可。因此,我们可以很容易的写出汉诺塔问题的代码:
void move(int count,int start,int finish,int temp)
{
if (count > 0){
move(count -1,start,temp,finish);
cout<<"Move disk"<<cout<<"from"<<<start<<"to"<<finish<<endl;
move(count-1,temp,finish,start);
}
}
深入理解
我们注意到,在解决汉诺塔问题时,最大的盘子始终只被移动一次
,这也是证明此种方法就是最优解的条件。我们可以设当有个盘子时最小移动步数为
F(n)
F
(
n
)
,现在我们想得到一个关于
n
n
的递推公式。
1. 在把个盘子从
A
A
移动到的过程中,必然存在一步,是把最大的盘子从
A
A
拿出来。要想把最大的盘子从移动到别的某个柱子上(
B
B
或),就必须保证剩下的
n−1
n
−
1
个盘子
C
C
或上。要保证
n−1
n
−
1
个盘子都在剩下那个柱子上,至少需要
F(n−1)
F
(
n
−
1
)
次移动。
2. 在将最大的盘子放到目标柱子上后,可以证明,最优解的情况下,最大的盘子不再移动。因此,需要将剩下的
n−1
n
−
1
个盘子放到目标柱子上,至少需要
F(n−1)
F
(
n
−
1
)
次移动。
3. 由于最大的盘子至少需要移动一次,因此,总花费为
F(n)=2×F(n−1)+1
F
(
n
)
=
2
×
F
(
n
−
1
)
+
1
。
很容易得到显式解 2n−1−1 2 n − 1 − 1 。
新汉诺塔问题
题目详解
这个题目与传统的汉诺塔问题很相似,但是其初始条件和终止条件都发生了变化,不再是单一的状态。由之前的分析可得,其最关键的思路是确保最大盘只移动一次
。而迁移到本题,我们可以想到:
从编号最大的盘开始数,如果其起始状态和终止状态都相同,则最优解中不需要移动这个盘。
我们可以用反证法很容易得到这个证明。因此,我们首先根据初始状态和终止状态,找出其状态不同的最大编号,记为
k
k
,那么必须移动。
现在又回到最初的思路,可以想象:当移动
k
k
的一瞬间,假设需要从
A
A
移动到,比
k
k
大的盘子不需要移动且不会影响我们的移动,可以当成不存在。比小的盘子既不能在
A
A
上,也不能在上,因此只能在
C
C
上。因此,当我们要移动时,状态必须只能是:在
A
A
上只有,在
B
B
上为空,其余盘子按照顺序在上。
由于盘子的移动是可逆的,因此我们可以分别求初始状态和终止状态到当前状态所需的最小步数之和即可。用函数表示就是
Func(start,k−1,6−start[k]−finish[k])+Func(finish,k−1,6−start[k]−finish[k])+1 F u n c ( s t a r t , k − 1 , 6 − s t a r t [ k ] − f i n i s h [ k ] ) + F u n c ( f i n i s h , k − 1 , 6 − s t a r t [ k ] − f i n i s h [ k ] ) + 1
其中,
6−start[k]−finish[k]
6
−
s
t
a
r
t
[
k
]
−
f
i
n
i
s
h
[
k
]
就是我们需要将
k−1
k
−
1
个盘子移动到的柱子(1或者2或者3)。
如何计算
Func(P,i,final)
F
u
n
c
(
P
,
i
,
f
i
n
a
l
)
呢?根据之前的推理,如果
P[i]=final
P
[
i
]
=
f
i
n
a
l
,则不需要移动,即为
Func(P,i,final)=Func(P,i−1,final)
F
u
n
c
(
P
,
i
,
f
i
n
a
l
)
=
F
u
n
c
(
P
,
i
−
1
,
f
i
n
a
l
)
;否则就要前
i−1
i
−
1
个盘子移动到
6−P[i]−final
6
−
P
[
i
]
−
f
i
n
a
l
做中转,然后将第
i
i
个盘子移动后,再将已经排好序的个盘子放入目标柱子。注意,根据之前经典汉诺塔问题的求解,最后一个步骤需要
2i−1−1+1=2i−1
2
i
−
1
−
1
+
1
=
2
i
−
1
步。由此可得,
Func(P,i,final)=Func(P,i−1,6−P[i]−final)+2n−1
F
u
n
c
(
P
,
i
,
f
i
n
a
l
)
=
F
u
n
c
(
P
,
i
−
1
,
6
−
P
[
i
]
−
f
i
n
a
l
)
+
2
n
−
1
。
代码示例
注意,此题需要使用long long
类型输出才可AC。
#include <iostream>
using namespace std;
long long recursive(int p[],int i,int target)
{
if (i == 0) return 0;
if (p[i] == target)
return recursive(p, i-1 ,target);
return recursive(p, i-1 , 6 - p[i] - target) + (1LL<<(i-1));
}
const int maxn = 60+5;
int main()
{
int start[maxn],finish[maxn],N;
int kcase = 1;
while (cin>>N && N)
{
for (int i = 1; i <= N; i++)
{
cin>>start[i];
}
for (int i = 1; i <= N; i++)
{
cin>>finish[i];
}
int k = N;
long long ans = 0;
while (k >= 1 && start[k] == finish[k])
{k--;
}
if (k >=1)
{
int other = 6 - start[k] - finish[k];
ans = recursive(start,k-1,other) + recursive(finish,k-1,other) + 1;
}
cout<<"Case "<<kcase++<<": "<<ans<<endl;
}
return 0;
}
欢迎关注我的个人博客。