1 条题解
-
0
题解
题意简述
给定两个排列 和 (长度 ),允许操作:选择 ,同时交换 与 、 与 。问能否在最多 次操作内使得 与 互为逆序,即:
若能,输出任意操作序列;若不能,输出 。
思路分析
1. 关键观察
每次操作交换位置 和 ,并同时交换 与 。换句话说,每一对数 是绑定在一起移动的,不会拆散。
最终状态要求 对所有 成立。因此对于位置 和 ,最终状态满足:
也就是说,最终排列中, 与 必须互为对方交换 的值。
2. 必要条件的推导
在初始排列中,如果存在某对数 ,则最终它必须与它的“互换对” 配对(占据对称的两个位置)。这意味着:
- 对于每个 ,若 存在,则 也必须存在(且数量对称)。
- 特别地,若 ,则数对 必须占据中心位置(当 为奇数时)。
因此,可行性检查:
- 偶数 :不能有 这样的自配对。
- 奇数 :必须有恰好一个 自配对,其它 个数对互成对称对。
若违反此条件,直接输出 。
3. 构造方法
我们用一个数组 表示当前 中每个值的下标,即 。初始根据输入的 建立 。
步骤 1:处理中心(仅当 为奇数)
令 为那个自配对 当前所在位置。我们需要把它移到中心位置 。
调用
work(x, (n+1)/2),交换位置 和中心,同时交换对应的 对。
步骤 2:处理对称配对
现在对 :
我们希望最终 。当前 吗?不一定, 只是数值,不一定在位置 。但我们可以利用 来定位:
我们要把 这个对子放到对称位置。具体操作:
- 因为 在最终 上应该等于 的对称位置的值,所以现在 在 数组中的当前位置是 。
- 我们想把位置 上的数对与 位置上的数对交换。
调用
work(p[b_i], n+1-i)。这样处理后,就能保证对所有已处理过的 ,有 且 。
步骤 3:操作计数
每次
work操作只在两个不同下标执行,且每次操作计数一次。总共最多 次操作(奇数时多一次处理中心),满足题目要求 。
4. 正确性验证
通过这种贪心构造,我们逐步将每一对对称位置匹配正确,且不会破坏已经匹配好的更小的 ,因为每次只交换两个位置,而 从小到大,已匹配位置是外侧,交换的内侧不会影响它们。
复杂度
每个测试用例 ,所有用例总和 不超过 ,可接受。
代码实现
#include <bits/stdc++.h> using namespace std; const int maxn = 200100; int n, a[maxn], b[maxn], m, p[maxn], ans[maxn][2]; inline void work(int x, int y) { if (x == y) return; ans[++m][0] = x; ans[m][1] = y; swap(a[x], a[y]); swap(p[a[x]], p[a[y]]); swap(b[x], b[y]); } void solve() { cin >> n; for (int i = 1; i <= n; ++i) { cin >> a[i]; p[a[i]] = i; } for (int i = 1; i <= n; ++i) { cin >> b[i]; } m = 0; int x = 0; // 可行性检查 for (int i = 1; i <= n; ++i) { if (a[i] == b[i]) { if (n % 2 == 0 || x) { cout << "-1\n"; return; } x = i; } else if (b[p[b[i]]] != a[i]) { cout << "-1\n"; return; } } // 奇数时,将自配对移到中心 if (n & 1) { work(x, (n + 1) / 2); } // 处理对称对 for (int i = 1; i <= n / 2; ++i) { work(p[b[i]], n - i + 1); } // 输出 cout << m << '\n'; for (int i = 1; i <= m; ++i) { cout << ans[i][0] << ' ' << ans[i][1] << '\n'; } } int main() { ios::sync_with_stdio(0); cin.tie(0); int T; cin >> T; while (T--) { solve(); } return 0; }
- 1
信息
- ID
- 7036
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 3
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者