10 UVA10795 A Different Task & 新汉诺塔问题 题解
A Different Task
题面
给定一个汉诺塔的初始局面以及目标局面,求将初始局面化为目标局面至少需要多少步?
\(1 \le T \le 100, 1 \le n \le 60\)
题解
这道题思想挺不错的。
我们不难想到先看大盘如何移动,因为我们传统汉诺塔中也是考虑最后一个大盘如何移动,因为如果大盘放到了相应的位置,小盘移动的时候不受影响。
我们找到第一个初始位置和目标位置不同的盘子,设为第 \(k\) 个盘子,那么所有 \(>k\) 的盘子都不需要管了,因为它们已经在对应位置,并且不会影响我们的后续移动,我们忽略这些不需要管的盘子。
我们设初始位置数组为 \(start\),目标位置数组为 \(end\)。
要将 \(k\) 从 \(start[k]\) 移动到 \(end[k]\),设 \(other = 6 - start[k] - end[k]\)。
满足 \(k\) 上面没有盘子,并且 \(end[k]\) 上没有盘子(忽略 \(>k\) 的盘子后),这也就等价于将 \(1 \sim k - 1\) 盘子从 \(start\) 局面移动到 \(other\) 上,然后将 \(k\) 移动到 \(end[k]\),再将 \(other\) 上的 \(1 \sim k - 1\) 移动到 \(end\) 局面。
我们设 \(f(p, x, final)\) 表示当前局面为 \(p\),将 \(1 \sim x\) 移动到 \(final\) 这个柱子的最小代价。
那么我们上面的计算过程可以写成:\(f(start, k - 1, other) + 1 + f(end, k - 1, other)\)
由于对称性,所以我们最后一步可以写成 \(f(end, k - 1, other)\),相当于将我们原来的过程倒着搞。
关于 \(f(p, x, final)\) 的计算:
边界 \(f(p, 0, final) = 0\)
如果 \(p[x] = final\),那么我们返回 \(f(p, x - 1, final)\)
否则返回 \(f(p, x - 1, 6 - p[x] - final) + 1 + (2^{x - 1} - 1)\),表示先将前 \(x - 1\) 个盘子移动到中转盘,然后将 \(x\) 放到 \(final\),最后再将前 \(x - 1\) 个盘子从中转盘移动到 \(final\),最后一步可以使用公式计算 \(g(n) = 2^n - 1\)
单次时间复杂度 \(O(n)\),总时间复杂度 \(O(Tn)\)
code
#include <bits/stdc++.h>
using namespace std;
namespace michaele {
#define rep(i, s, t) for (int i = s; i <= t; i ++)
#define irep(i, s, t) for (int i = s; i >= t; i --)
typedef pair <int, int> pii;
typedef long long ll;
const int N = 70;
int n;
int start[N], end[N];
ll dfs (int *p, int x, int final) {
if (x == 0) return 0;
if (p[x] == final) return dfs (p, x - 1, final);
int other = 6 - p[x] - final;
return dfs (p, x - 1, other) + (1ll << x - 1);
}
void solve () {
int cntC = 0;
while (cin >> n) {
if (!n) break;
rep (i, 1, n) cin >> start[i];
rep (i, 1, n) cin >> end[i];
int k = n;
while (k && start[k] == end[k]) k --;
ll ans = 0;
if (k >= 1) {
int other = 6 - start[k] - end[k];
ans = dfs (start, k - 1, other) + dfs (end, k - 1, other) + 1;
}
printf ("Case %d: %lld\n", ++ cntC, ans);
}
}
}
int main () {
michaele :: solve ();
return 0;
}

浙公网安备 33010602011771号