题解:CF1682E Unordered Swaps
posted on 2025-02-21 12:55:23 | under | source
题意:给定排列 \(p_1\dots p_n\) 与操作集合 \((x_1,y_1)\dots (x_m,y_m)\),找出一种操作顺序,满足按顺序交换 \(p_x,p_y\) 最终排列升序。保证一定有解,且 \(m\) 是交换任意两元素使得排列升序的最小次数。\(m<n\le 2\times 10^5\)。
\(i\to p_i\) 建出置换环,交换 \(p_x,p_y\) 等价于交换 \(x,y\) 出边。由于次数最小,所以每次操作时 \(x,y\) 不可能在两个不同的环上。
再次建图,\(x_i,y_i\) 连一条无向边,容易发现不存在环,因为有环的话必然存在一个操作交换两个不同环。所以得到是森林。
那么问题转化为:一棵树带点权,按顺序对边操作交换两端点权,最终点权等于点编号。
考虑 \(i\to p_i\) 路径,要将 \(i\) 上的点权运向 \(p_i\),那么路径上的边操作顺序严格递增。想到将边视为点建有向图,跑一边拓扑排序。
但是会不会出现点权 \(p_i\) 在运输时被路径外的交换操作给交换走了呢?实际上是不会的。
证明:考察路径上相邻两条边 \(u\to v\) 和 \(v\to w\) 称为 \(x_1,x_2\),记 \(t_x\) 为 \(x\) 操作顺序。由于 \(i\to p_i\) 构成简单环,尝试观察该环在树上的形态,首先两条方向相反的链不能有 \(\ge 2\) 的相交部分否则非法,那么拎起 \(i\to p_i\) 路径挂着一些子树。从 \(i\) 跳到 \(p_i\) 后,遍历 \(p_i\) 子树,然后跳到 \(p_i\) 路径前一位的子树,以此类推子树倒序遍历。对于 \(v\) 子树的遍历,一定是后面跳到 \(v\) 的某一个子树,然后再从该子树开始依次遍历 \(v\) 的其它子树,最终跳向 \(u\) 子树。此过程中,\(v\) 的相邻边 \(y_1\dots y_q\) 被依次跨越,最终从 \(y_q\) 跨越到 \(x_1\),所以有 \(t_{y_1}<t_{y_2}\dots t_{y_q}<x_1<x_2\),因此不可能出现上述情况。
于是暴力建图跑拓扑序即可,同时由上述证明知一条边恰被两条链覆盖,所以是 \(O(n)\) 的。
代码
#include<bits/stdc++.h>
using namespace std;
#define pir pair<int, int>
const int N = 2e5 + 5;
int n, m, x, y, p[N], d[N], fa[N], faw[N], in[N];
vector<pir> to[N];
vector<int> to2[N];
inline void dfs(int u, int from){
d[u] = d[from] + 1;
for(auto _ : to[u]){
int v = _.first;
if(v ^ from) fa[v] = u, faw[v] = _.second, dfs(v, u);
}
}
inline void dist(int s, int t){
vector<int> l0, l1;
if(d[s] < d[t]) while(d[s] < d[t]) l1.push_back(faw[t]), t = fa[t];
if(d[s] > d[t]) while(d[s] > d[t]) l0.push_back(faw[s]), s = fa[s];
while(s ^ t) l0.push_back(faw[s]), l1.push_back(faw[t]), s = fa[s], t = fa[t];
int s0 = l0.size(), s1 = l1.size();
for(int i = 0; i < s0 - 1; ++i) to2[l0[i]].push_back(l0[i + 1]);
if(!l1.empty()){
reverse(l1.begin(), l1.end());
if(!l0.empty()) to2[l0[s0 - 1]].push_back(l1[0]);
for(int i = 0; i < s1 - 1; ++i) to2[l1[i]].push_back(l1[i + 1]);
}
}
inline void topo(){
for(int i = 1; i <= m; ++i)
for(auto j : to2[i]) ++in[j];
queue<int> q;
for(int i = 1; i <= m; ++i)
if(!in[i]) q.push(i);
while(!q.empty()){
int u = q.front(); q.pop();
printf("%d ", u);
for(auto v : to2[u])
if(!(--in[v])) q.push(v);
}
}
signed main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i) scanf("%d", &p[i]);
for(int i = 1; i <= m; ++i) scanf("%d%d", &x, &y), to[x].push_back({y, i}), to[y].push_back({x, i});
for(int i = 1; i <= n; ++i) if(!d[i]) dfs(i, 0);
for(int i = 1; i <= n; ++i) dist(i, p[i]);
topo();
return 0;
}

浙公网安备 33010602011771号