汉诺塔问题
题目描述:
给定一个整数n,代表汉诺塔游戏中从小到大放置n个圆盘,假设开始所有圆盘都在左边的柱子上,那么用最优的办法把所有圆盘都移动到右边的柱子上的过程,就称为最优移动轨迹。给定一个整型数组arr, 其中只含有1、2和3,代表所有圆盘目前的状态,1代表左柱,2代表中柱,3代表右柱,a[i]的值代表第i+1个圆盘的位置(a[i]下标从0开始)。比如,arr=[3,3,2,1], 代表第1个圆盘在右柱上、第2个圆盘在右柱上、第3个圆盘在中柱上、第4个圆盘在左柱上。如果arr代表的状态是最优移动轨迹过程中出现的状态,输出arr这种状态是最优移动轨迹中的第几个状态(由于答案可能较大,请输出对 1 0 9 + 7 10^9+7 109+7 取模后的答案)。如果arr代表的状态不是最优移动轨迹过程中出现的状态,则输出-1。
输入描述:
输入包括两行,第一行一个整数n ( 1 ≤ n ≤ 2 ∗ 1 0 6 ) ( 1 \leq n \leq 2*10^6) (1≤n≤2∗106),表示圆盘的个数,第二行n个正整数,且均为1或2或3,第i个整数表示第i个圆盘位置。
输出描述:
输出一个整数,表示这种状态是第几个最优移动状态(输出对 1 0 9 + 7 10^9+7 109+7 取模后的答案),无解输出-1。
示例1
输入
2
1 1
输出
0
示例2
输入
2
3 3
输出
3
备注:
时间复杂度 O ( n ) O(n) O(n) ,空间复杂度 O ( n ) O(n) O(n) 。
题解:
汉诺塔最优移动步骤为:
- 步骤1:把 1~n-1 从 from 借助 to 移动到 mid;
- 步骤2:把 n 从 from 移动到 to;
- 步骤3:把 1~n-1 从 mid 借助 from 移动到 to。
那么把 n 个圆盘从 from 移动到 to 的最少移动步数是多少呢?
设为 F(n),则 F ( n ) = 步 骤 1 的 总 步 数 + 1 + 步 骤 3 的 总 步 数 F(n)=步骤1的总步数 + 1 + 步骤3的总步数 F(n)=步骤1的总步数+1+步骤3的总步数,即$ F(n)=F(n-1) + 1 + F(n-1)$。
又 F(1)=1,则 F ( n ) + 1 = 2 ( F ( n − 1 ) + 1 ) F(n)+1=2(F(n-1)+1) F(n)+1=2(F(n−1)+1),等比数列求和可以得到: F ( n ) + 1 = 2 n F(n)+1=2^n F(n)+1=2n 。
回到本题上来,我们从 n 号盘着手,分为三种情况:
- n 在 from 上,则需要判断 1~n-1 号盘的情况,其目标是上述步骤1;
- n 在 to 上,说明起码走了 2 n − 1 2^{n-1} 2n−1 步,需要考察剩下的 1~n-1 号盘的情况,其目标是上述步骤3;
- n 在 mid 上,这种情况在最优移动中是不存在的,直接返回 -1 即可。
此题要求空间复杂度为 O ( n ) O(n) O(n) ,递归需要额外的空间,所以把递归换成非递归即可。
注意:此题可以提前预处理 2 i 2^i 2i,对 mod 进行取余,防止计算过程中出现溢出。
代码:
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 2e6 + 10;
const int MOD = 1e9 + 7;
int n;
int a[N];
int two_power[N];
int main(void) {
scanf("%d", &n);
int t = 1;
for (int i = 0; i < n; ++i) {
scanf("%d", a + i);
two_power[i] = t;
t <<= 1;
if (t >= MOD) t -= MOD;
}
int from = 1, mid = 2, to = 3;
int now = n - 1;
int ret = 0;
while (now >= 0) {
if (a[now] == mid) {
return 0 * puts("-1");
}
if (a[now] == to) {
ret = (ret + two_power[now]) % MOD;
swap(from, mid);
} else swap(mid, to);
--now;
}
return 0 * printf("%d\n", ret);
}