CF2232D题解

传送门:https://www.luogu.com.cn/problem/CF2232D

题意:汉诺塔问题强化版,第 \(i\) 层当且仅当上方有 \(a_i\) 层才能被移动,限制最多 \(2^n\) 次。

照搬汉诺塔的做法显然不可行。进一步发掘性质:一层蛋糕是否能搬动只和上面的蛋糕有关。

我们可以优先搬动最后一层蛋糕到 \(3\) 号柱。在此之前需要先把前 \(a_n\) 层蛋糕移动到 \(2\) 号柱。

如果不能做到问题显然无解,否则我们把第 \(n\) 层移动到 \(3\) 号柱后把前 \(a_n\) 个柱子还原移动到 \(1\) 号柱,因为 \(n\) 层是最大的且已经移动到 \(3\) 号目标柱,所以不会对复原后的 \(n-1\) 个柱子产生任何影响。我们再以相同的手法从大到小依次处理剩下的 \(n-1\) 层即可。

我们根据上述步骤发现有解当且仅当所有的 \(a_i<i\)

具体实现:

设当前的递归状态 \(f_{i,src,tar}\)。表示将前 \(i\) 层从 \(src\) 柱移动到 \(tar\) 柱。

初始 \(f_{n,1,3}\)

  1. 将前 \(a_i\) 层移动到过渡柱,递归 \(f_{a_i,src,src\oplus tar}\)
  2. \(src\) 移动最后一层 \(i\)\(tar\)
  3. 接着还原前 \(a_i\) 层,递归\(f_{a_i,src\oplus tar,src}\)
  4. 最后移动剩下 \(i-1\) 层,递归 \(f_{i-1,src,tar}\)

跑一遍代码发现在样例的标准的 \(3\) 层汉诺塔问题用了 \(13\) 步。

我们分析一下次数,设 \(g_i\) 表示前 \(i\) 次花的步数。

标准汉诺塔问题下我们的算法:\(g_i=2*g_{i-1}+g_{i-1}+1\) 远大于标准汉诺塔的:\(g_i=2*g_{i-1}+1\)

我们发现导致问题的根本原因是我们做了一次冗余的复原即第三步,我们完全可以在过渡柱上处理完整的前 \(i-1\) 个柱子的子问题,只有当 \(a_i\neq i-1\) 时前 \(i-1\) 个柱子没有被完整移动时时再复原成前 \(i-1\) 个完整柱子。

新的步骤:

  1. 将前 \(a_i\) 层移动到过渡柱,递归 \(f_{a_i,src,src\oplus tar}\)
  2. \(src\) 移动最后一层 \(i\)\(tar\)
    • 如果 \(a_i\neq i-1\) 还原前 \(a_i\) 层,递归\(f_{a_i,src\oplus tar,src}\)
    • 否则跳过第 \(4\) 步直接递归 \(f_{i-1,src\oplus tar,src}\)
  3. 最后移动剩下 \(i-1\) 层,递归 \(f_{i-1,src,tar}\)

再次分析次数:

\[g_i=\begin{cases}g_i=2*g_{i-1}+1 & a_i=i-1 \\ g_i=2*g_{a_i}+g_{i-1}+1 &a_i< i-1 \end{cases} \]

应用数学归纳法:

\(h_i\) 是标准汉诺塔前 \(i\) 层的最小移动次数。有递归式 \(h_i=h_{i-1}*2+1\),也有通项公式 \(h_i=2^i-1\)

\(h_0=g_0=0,h_1=g_1=1\)。有\(g_0\leq h_0,g_1\leq h_1\)

对于 \(i\geq 2\)

已知第 \(i-1\) 层满足 \(g_{i-1}\leq h_{i-1}\)

  • 如果 \(a_i=i-1\)

\[\begin{align*}g_i&=g_{i-1}*2+1\\ g_i&\leq h_{i-1}*2+1\\ g_i&\leq h_i \end{align*} \]

  • 如果 \(a_i<i-1\)

\[g_{a_i}\leq g_{i-2} \]

\[\because h_j=h_{j-1}*2+1\ \ \therefore h_{j-1}\leq\frac{h_j-1}{2} \]

\[g_{a_i}\leq g_{i-2}\leq\frac{h_{i-1}-1}{2} \]

\[\begin{align*}g_i&=2*g_{a_i}+g_{i-1}+1\\ g_i&\leq 2*g_{a_i}+h_{i-1}+1\\ g_i&< h_{i-1}*2+1\\ g_i&<h_i\end{align*} \]

综上 \(h_i=2^i-1\geq g_i\) 恒成立。当前仅当在标准汉诺塔问题下取等号。\(\square\)

#include <bits/stdc++.h>

using namespace std;

int a[21];

vector<pair<pair<int, int>, int> > ans;

void dfs(int x, int src, int tar) {
    if (!x) return;
    if (x == 1) return ans.push_back(make_pair(make_pair(x, src), tar));
    dfs(x - a[x] - 1, src, src ^ tar);
    ans.push_back(make_pair(make_pair(x, src), tar));
    if (!a[x]) dfs(x - 1, src ^ tar, tar);
    else dfs(x - a[x] - 1, src ^ tar, src), dfs(x - 1, src, tar);
}

void solve() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; ++i) cin >> a[i];
    for (int i = 1; i <= n; ++i) if (a[i] >= i) return cout << "NO\n", void();
    dfs(n, 1, 3);
    cout << "YES\n" << ans.size() << '\n';
    for (auto k: ans) cout << k.first.first << ' ' << k.first.second << ' ' << k.second << '\n';
    ans.clear();
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int T;
    cin >> T;
    while (T--) solve();
    return 0;
}
posted @ 2026-06-18 02:17  Jefferyzzzz  阅读(2)  评论(0)    收藏  举报