汉诺塔问题

汉诺塔问题

题目描述:

给定一个整数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) (1n2106),表示圆盘的个数,第二行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(n1)+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} 2n1 步,需要考察剩下的 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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值