传送门:https://www.luogu.com.cn/problem/CF2232D
题意:汉诺塔变种问题,第 \(i\) 层当且仅当上方有 \(a_i\) 层才能被移动。
照搬汉诺塔的做法显然不可行。进一步发掘性质:一层蛋糕是否能搬动只和上面的蛋糕有关。
我们可以优先搬动最后一层蛋糕到 \(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}\)
- 将前 \(a_i\) 层移动到过渡柱,递归 \(f_{a_i,src,src\oplus tar}\) 。
- 从 \(src\) 移动最后一层 \(i\) 到 \(tar\)。
- 接着还原前 \(a_i\) 层,递归\(f_{a_i,src\oplus tar,src}\) 。
- 最后移动剩下 \(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\) 个柱子的子问题,只有当 \(a_i\neq i-1\) 时移动柱子不完整时再复原。
新的步骤:
- 将前 \(a_i\) 层移动到过渡柱,递归 \(f_{a_i,src,src\oplus tar}\) 。
- 从 \(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}\)。
- 最后移动剩下 \(i-1\) 层,递归 \(f_{i-1,src,tar}\) 。
再次分析次数:
应用数学归纳法:
设 \(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\):
- 如果 \(a_i<i-1\):
综上 \(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;
}
