题解: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;
}

posted @ 2026-01-15 08:16  Zwi  阅读(3)  评论(0)    收藏  举报