F - Swap and Sort
题意:
给定一个
N
N
N 的排列
P
P
P 和
m
m
m 个操作,每个操作给定两个数
a
,
b
a,b
a,b,表示
P
[
a
]
P[a]
P[a] 和
P
[
b
]
P[b]
P[b] 可以交换位置,每次可以选定任意操作执行,判断能否把这个排列变成升序的,如果能,则按顺序输出操作的编号,否则输出
−
1
-1
−1
思路:
号码牌
ACWing上这道题目的加强版
判断是否存在解,只需要用并查集扫一遍判断当前位置的
P
[
i
]
P[i]
P[i] 与
i
i
i 是否位于同一个连通块,如果某个位置的
P
[
i
]
P[i]
P[i] 和
i
i
i 不在一个连通块则无解
这道题
D
F
S
DFS
DFS 才是难点所在
首先要明确复原的思路,对于一个连通块,我们每次复原一个点,然后删去,这时候要保证剩下的图是连通的才能复原成功。因此我们可以想到从叶子节点开始复原,每次复原一个叶子节点,复原后剩下的点继续复原时并不需要经过这个叶子节点,复原后的点相当于被删除掉。
因此选定任一连通块,然后不断递归到叶子节点开始依次复原
code:
#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 5e5 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
int p[maxn], f[maxn];
bool vis[maxn];
vector <int> e[maxn], ans;
map <pair<int,int>, int> id;
int find(int x){
return f[x] == x ? x : f[x] = find(f[x]);
}
bool dfs(int x, int y, int fa){// 带fa判断的写法,只能用于遍历树,遍历图如果存在环就会死循环
if(p[x] == y) return 1;
for(auto d : e[x]) if(d != fa)
{
if(dfs(d, y, x))
{
swap(p[x], p[d]);
ans.push_back(id[{x, d}]);
return 1;
}
}
return 0;
}
void leaf(int x){
vis[x] = 1;
for(auto y : e[x]) if(!vis[y])
leaf(y);
dfs(x, x, -1);// 以x位置为起点,寻找p[y]=x的位置y,搜到终点y时会形成一条路径
// 回溯时不断交换即可完成x位置的复原,这时x就可以删掉了,剩下的点的复原与x位置无关
}
void work()
{
cin >> n;
for(int i = 1; i <= n; ++i) cin >> p[i], f[i] = i;
cin >> m;
for(int i = 1, a, b; i <= m; ++i){
cin >> a >> b;
ll x = find(a), y = find(b);// 下边建图需要用到a,b,因此用临时变量存一下祖先节点
if(x != y)
{
f[x] = y;
e[a].push_back(b); e[b].push_back(a);
id[{a, b}] = id[{b, a}] = i;
}
}
for(int i = 1; i <= n; ++i){
int x = find(i), y = find(p[i]);
if(x != y){
cout << -1 << endl;return;
}
}
for(int i = 1; i <= n; ++i) if(!vis[i])
leaf(i);
cout << ans.size() << endl;
for(auto x : ans) cout << x << " ";
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}