1 条题解
-
0
题解:Shift 积木排序问题
问题描述
我们有 n 块编号为 1 到 n 的积木,初始时按某种顺序排列。我们可以进行两种操作: • 操作 a:将最后一个积木移到最前面
• 操作 b:将第三个积木移到最前面
目标是使积木按编号 1,2,\dots,n 的顺序排列。需要输出操作序列,要求:
- 块数 m \le n^2
- 相邻操作块类型不同
- 每块操作次数 0 < k < n
算法思路
基本观察
- 操作 a 实际上是循环右移 1 位
- 操作 b 实际上是将第 3 个元素移动到最前面
核心构造策略
我们采用归纳构造法:假设我们已经将前 i-1 个积木放到了正确的位置,现在要将第 i 个积木放到位置 i。
主要步骤
- 定位目标:找到数字 i 当前的位置
- 移动目标到末尾:通过操作 a 将目标移到序列末尾
- 调整位置:通过一系列 a 和 b 操作的组合,将目标插入到正确位置
- 重复处理:直到前 n-2 个积木都在正确位置
特殊情况处理
当处理到只剩下最后两个位置时(n-1 和 n): • 如果最后两个数已经正确(n-1 在 n 前面),则完成
• 如果 a[n-1] = n(即 n 在 n-1 前面),需要特殊处理:
• 如果 n 是奇数,无解
• 如果 n 是偶数,可以通过特定操作序列解决
算法详细步骤
预处理
- 输入序列 a[1..n]
- 初始化答案序列 ans
主循环(处理 i=1 到 n-2)
对于每个 i:
-
如果 a[i] 已经是 i:直接跳过
-
否则: • 找到数字 i 的位置 pos
• 执行操作:(n-pos+1)a 将 i 移到末尾
• 将序列重新排列
• 如果 i=1,继续处理下一个
• 否则:
◦ 找到数字 i-1 的位置
◦ 执行一系列 a 和 b 操作调整位置
◦ 执行 (i-1)a 完成当前调整
◦ 重新排列序列
最后两个数的处理
如果 a[n-1] = n(顺序不对): • 如果 n 是奇数:输出无解
• 如果 n 是偶数:
• 执行 2a
• 重复执行 2a 1b 共 \frac{n}{2}-1 次
• 执行 (n-1)a
输出优化
合并连续的相同操作,并确保 0 < k < n
正确性证明
- 操作的可逆性:两种操作都是可逆的
- 归纳法的正确性:通过构造性方法,可以证明对于任意排列,要么有解,要么在奇偶性条件下无解
- 操作次数限制:每次处理一个数最多需要 O(n) 次操作,总共 O(n^2) 次
复杂度分析
• 时间复杂度:O(n^3)(但实际运行很快)
• 空间复杂度:O(n^2) 存储操作序列
关键难点
- 构造性证明:需要找到具体的构造方法
- 奇偶性判断:当 n 为奇数且最后两个数位置交换时无解
- 操作序列合并:需要满足输出格式要求
示例分析
样例1
输入: 1 3 2 4
初始:[1,3,2,4]
步骤:
-
数字1已在位置1
-
处理数字2: • 2在位置3
• 执行 2a:[2,4,1,3]
• 调整位置:最终得到 [1,2,3,4]
输出: 3a 2b 2a 2b
代码实现细节
#include <bits/stdc++.h> using namespace std; const int N = 2e3; int n; int a[N + 5], b[N + 5];
struct Opt { int t; // 操作次数 char ch; // 操作类型 }; vector ans, res;
int main() { ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i]; // 处理前n-2个数 for (int i = 1; i <= n - 2; i++) { if (a[i] == i) continue; // 已在正确位置 // 找到数字i的位置 int pos = -1; for (int j = 1; j <= n; j++) { if (a[j] == i) { pos = j; break; } } // 将i移到末尾 ans.push_back({n - pos + 1, 'a'}); // 重新排列数组 int cnt = 0; for (int j = pos; j <= n; j++) b[++cnt] = a[j]; for (int j = 1; j < pos; j++) b[++cnt] = a[j]; memcpy(a, b, sizeof a); if (i == 1) continue; // 找到i-1的位置 for (int j = 1; j <= n; j++) { if (a[j] == i - 1) { pos = j; break; } } int r = pos + 1; // 执行一系列a和b操作调整位置 while (pos + 2 <= n) { ans.push_back({2, 'a'}); ans.push_back({1, 'b'}); pos += 2; } if (pos != n) { ans.push_back({1, 'a'}); ans.push_back({2, 'b'}); } ans.push_back({i - 1, 'a'}); // 重新构造数组 cnt = 0; for (int j = 1; j <= i; j++) b[++cnt] = j; for (int j = r; j <= n; j++) b[++cnt] = a[j]; for (int j = 1; a[j] != 1; j++) { if (a[j] == i) continue; b[++cnt] = a[j]; } if (pos != n) swap(b[i + 1], b[i + 2]); memcpy(a, b, sizeof a); } // 处理最后两个数 if (a[n - 1] == n) { if (n & 1) { // n为奇数,无解 cout << "NIE DA SIE"; return 0; } ans.push_back({2, 'a'}); int cnt = n / 2 - 1; while (cnt--) { ans.push_back({2, 'a'}); ans.push_back({1, 'b'}); } ans.push_back({n - 1, 'a'}); } // 合并相同操作 for (int i = 0; i < ans.size(); i++) { if (res.empty()) { res.push_back(ans[i]); continue; } if (res.back().ch == ans[i].ch) { res.back().t += ans[i].t; res.back().t %= n; if (res.back().t == 0) res.pop_back(); } else { res.push_back(ans[i]); } } // 输出结果 cout << res.size() << "\n"; for (int i = 0; i < res.size(); i++) { cout << res[i].t << res[i].ch << " "; } return 0;}
- 1
信息
- ID
- 5972
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 21
- 已通过
- 1
- 上传者