Swap and Sort
题意
有一个 \(1 \dots n\) 的全排列 \(p_1 \dots p_n\)。
有 \(m\) 种操作,第 \(i\) 种操作可以交换 \(p_{a_i}\) 和 \(p_{b_i}\)
请问最多执行 \(10^5\) 次操作能否使得 \(p\) 升序排列。
- 如果可能,第一行输出操作次数,第二行输出操作序列
- 否则,输出
-1
数据范围
- \(2 \leqslant n \leqslant 1000\)
- \(1 \leqslant m \leqslant \min ( 2 \times 10 ^ 5, \frac{n(n-1)}{2})\)
- \(1 \leqslant a_i < b_i \leqslant n\) 且当 \(i \neq j\) 时 \((a_i,b_i) \neq (a_j,b_j)\)
思路
建图
详见代码注释。
次数限制分析
首先来分析一下:
- 同一个操作执行了偶数次,就相当于没有执行
- 同一个操作执行了奇数次,就相当于执行\(1\)次
所以在操作次数尽量小的情况下,最多执行 \(\frac{n ^ 2}{2}\) 次。
然后就会惊喜地发现:\(\frac{1000^2}{2}\) 就是 \(5 \times 10 ^ 5\),也就是说,只要每个操作都是有意义的且在不限定次数的情况下是可以完成的,就是可以完成的。
图->树
接下来就需要把有环图转成树了。
由于我们分析了次数限制不是很重要,所以是可以断掉环上的任意的边的,因为不需要操作数量最小。
求答案
找答案其实很简单,只要注意细节,为了防止在交换之后原本处理好的节点重新被交换到了一个错误的地方,我们只能去求每棵树的叶子节点。
复杂度
- 时间:\(O(n+m)\)
- 空间:\(O(n+m)\)
Code
点击查看代码
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
const int M = 2e5 + 10;
struct node {
int x, id;
};
int n, m, a[1010], f[1010], l, r, num, b[1010], fa[1010];
vector<node> nv[1010], v[1010];
vector<int> ans;
queue<int> q;
void stree (int x) {
f[x] = 1; // 标记这个节点在图中
for (auto i : nv[x]) {
if (!f[i.x]) {
v[x].push_back({i.x, i.id}), v[i.x].push_back({x, i.id}), b[x]++, fa[i.x] = x, stree(i.x); // 记录这个节点的儿子
}
}
}
void dfs (int x, int y, int sum) {
if (a[x] == l) { // 终于交换到了目的地
num = sum; // 记录答案
return ;
}
for (auto i : v[x]) {
if (i.x != y) { // 不是上一个节点
dfs(i.x, x, sum + 1); // 进行dfs
}
if (num != -1) { // 已经交换到了目的地
ans.push_back(i.id);
swap(a[x], a[i.x]); // 记得真正地交换一下
return ;
}
}
}
void Ans () { // 求答案
for (int i = 1; i <= n; i++) {
if (!b[i]) { // 是叶子节点
q.push(i);
}
}
while (!q.empty()) {
l = q.front(), num = -1, q.pop();
dfs(l, 0, 0);
if (num == -1) { // 到不了目标点
cout << -1;
exit(0);
}
b[fa[l]]--; // 当前点没了,它的父亲的儿子数量要减1
if (!b[fa[l]]) { // 它的父亲成了叶子节点
q.push(fa[l]); // 加入答案处理队伍
}
}
}
int main(){
ios::sync_with_stdio(0), cin.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
cin >> m;
for (int i = 1; i <= m; i++) {
cin >> l >> r, nv[r].push_back({l, i}), nv[l].push_back({r, i}); // 记录交换
}
for (int i = 1; i <= n; i++) {
if (!f[i]) {
stree(i); // 生成树
}
}
Ans();
cout << ans.size() << '\n';
for (auto i : ans) {
cout << i << ' '; // 处理操作序列
}
return 0;
}