[NOIP2007-普及] Hanoi双塔问题

GitHub跳转原题关系图返回列表

汉诺塔双塔版:同尺寸两盘看作整体,A_n = 2A_{n-1}+2,通项 2^{n+1}-2,高精度输出。

OJ: roj

题目 ID: 3555

难度:普及-

标签:递推数学高精度

创建: 2026-10-02 07:19

更新: 2026-10-06 13:51

题目描述

AA、BB、CC 三根柱子,AA 柱上叠着 2n2n 个圆盘,共 nn 种尺寸,每种尺寸恰好两个相同且不加区分的圆盘。每次只能移动某柱最顶端的一个圆盘到另一柱顶,任何时刻三根柱都要保持上小下大。求把全部 2n2n 个盘移到 CC 柱的最少移动次数 AnA_n。

输入一个正整数 nn(1≤n≤2001 \le n \le 200),输出 AnA_n。

样例输入 1:1,输出 2;样例输入 2:2,输出 6。

思路

把最大的 2 个盘看成整体:先花 An−1A_{n-1} 步把上面 2(n−1)2(n-1) 个盘搬到 BB,再各用 1 步把两个最大盘落到 CC 底,最后再花 An−1A_{n-1} 步把 BB 上的盘搬回 CC,得 An=2An−1+2A_n = 2A_{n-1} + 2,A1=2A_1 = 2。展开递推得通项 An=2n+1−2A_n = 2^{n+1} - 2。n=200n = 200 时约有 61 位十进制,超出 64 位整数,需要手写高精度。

参考代码

Python 版:

python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-02 07:19
# update_at: 2026-10-02 07:19

import sys


def solve() -> None:
    data = iter(map(int, sys.stdin.buffer.read().split()))
    n = next(data)
    # 递推 A_k = 2*A_{k-1} + 2,通项 A_n = 2^(n+1) - 2
    print((2 << n) - 2)


if __name__ == "__main__":
    solve()

C++ 版(同一算法):

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-10-06 13:51
 * update_at: 2026-10-06 13:51
 */
#include <cstdio>

typedef long long ll;

const int MAXL = 70; // n=200 时 2^201-2 约有 61 位十进制,开 70 位足够
const int BASE = 10; // 每位存一个十进制数字

int len;         // 高精度数的位数
int dig[MAXL];   // dig[i] 表示 10^i 位上的数字

// 高精度乘 2:直接按位翻倍再处理进位
void mul2() {
    int carry = 0;
    for (int i = 0; i < len; i++) {
        int cur = dig[i] * 2 + carry;
        dig[i] = cur % BASE;
        carry = cur / BASE;
    }
    if (carry > 0) {
        dig[len] = carry;
        len++;
    }
}

int main() {
    ll n; // 盘子的种类数
    scanf("%lld", &n);

    // 答案为 2^(n+1) - 2:先存数字 1,再乘 n+1 次 2
    dig[0] = 1;
    len = 1;
    for (ll i = 0; i <= n; i++) mul2();

    // 减 2(结果保证非负,因为 n >= 1 时 2^(n+1) >= 4)
    dig[0] -= 2;
    for (int i = 0; i < len; i++) {
        if (dig[i] < 0) {
            dig[i] += BASE;
            dig[i + 1] -= 1;
        }
    }
    while (len > 1 && dig[len - 1] == 0) len--; // 去掉前导零

    for (int i = len - 1; i >= 0; i--) printf("%d", dig[i]);
    printf("\n");
    return 0;
}