CF2062G Permutation Factory 题解

Description

给定两个长度为 nn 的排列 p1,p2,,pnp_1, p_2, \ldots, p_nq1,q2,,qnq_1, q_2, \ldots, q_n。每次操作中,你可以选择两个不同的整数 1i,jn1 \leq i, j \leq n 并交换 pip_ipjp_j。该操作的成本为 min(ij,pipj)\min(|i - j|, |p_i - p_j|)

请找到使 pi=qip_i = q_i 对所有 1in1 \leq i \leq n 成立的最小总成本,并输出达成该目标的交换操作序列。

一个长度为 nn 的排列是由 11nn 的不同整数按任意顺序组成的数组。例如,[2,3,1,5,4][2, 3, 1, 5, 4] 是排列,但 [1,2,2][1, 2, 2] 不是排列(22 重复出现),[1,3,4][1, 3, 4] 也不是排列(当 n=3n=3 时出现 44)。

n100n\leq 100

Solution

首先每次操作的成本是两个东西的 min\min,而最后又要最小化总成本,所以这两维可以拆开。

考虑把排列看成 nn 个点,每个为 (i,pi)(i,p_i),每次有两种操作:

  1. 选择 i,ji,j,用 ij|i-j| 的代价将 (i,pi)(i,p_i) 移到 (j,pi)(j,p_i)(j,pj)(j,p_j) 移到 (i,pj)(i,p_j)
  2. 选择 i,ji,j,用 pipj|p_i-p_j| 的代价将 (i,pi)(i,p_i) 移到 (i,pj)(i,p_j)(j,pj)(j,p_j) 移到 (j,pi)(j,p_i)

容易发现一次操作的代价就是移动的曼哈顿距离除以二,所以我们只关心移动的总曼哈顿距离。

由于最终所有 (j,qj)(j,q_j) 上需要有点,所以一定是一一匹配的,不妨设最终是初始的 (i,pi)(i,p_i) 移动到了 (mi,qmi)(m_i,q_{m_i}),那么有一个显然的下界是 i=1n(imi+piqmi)\sum_{i=1}^{n}{\left(|i-m_i|+|p_i-q_{m_i}|\right)},可以证明这个下界是可以取到的。

先用费用流求出 mim_i

构造就考虑两位实际上是独立的,而每一维等价于是有一个大小为 nn 的排列 pp,可以通过 ij|i-j| 的代价交换 pip_ipjp_j,要求最后 pi=pjp_i=p_j

这个是比较经典的问题,每次选择最小的 ii 使得 piip_i\neq i,然后找到 pj=ip_j=i 的这个 jj,容易发现 pi,pi+1,,pj1p_i,p_{i+1},\ldots,p_{j-1} 一定存在一个数大于等于 jj,找到这个位置交换即可,总共做至多 n(n1)2\frac{n(n-1)}{2} 次,因为每次会至少减少两个逆序对。

时间复杂度:O(n4)O(n^4)

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 105;

int n;
int p[kMaxN], q[kMaxN], match1[kMaxN], match2[kMaxN];
std::vector<std::pair<int, int>> vec;

namespace Dinic {
const int kMaxN = 205, kMaxM = kMaxN * kMaxN * 4, kInf = 1e9;

struct Node {
int v, w, c, pre;
} e[kMaxM];

int tot = 1, n, s, t, cost;
int tail[kMaxN], dis[kMaxN], cur[kMaxN], eid[kMaxN][kMaxN];
bool inq[kMaxN], vis[kMaxN];

void init(int _n, int _s, int _t) {
for (int i = 1; i <= n; ++i) tail[i] = 0;
tot = 1, n = _n, s = _s, t = _t;
}

void adde(int u, int v, int w, int c) { e[++tot] = {v, w, c, tail[u]}, tail[u] = eid[u][v] = tot; }
void add(int u, int v, int w, int c) { adde(u, v, w, c), adde(v, u, 0, -c); }

bool spfa() {
std::queue<int> q;
for (int i = 1; i <= n; ++i) {
dis[i] = kInf, cur[i] = tail[i], inq[i] = vis[i] = 0;
}
q.emplace(s), dis[s] = 0, inq[s] = 1;
for (; !q.empty();) {
int u = q.front(); inq[u] = 0, q.pop();
for (int i = tail[u]; i; i = e[i].pre) {
int v = e[i].v, w = e[i].w, c = e[i].c;
if (w && dis[v] > dis[u] + c) {
dis[v] = dis[u] + c;
if (!inq[v]) q.emplace(v), inq[v] = 1;
}
}
}
return dis[t] != kInf;
}

int dfs(int u, int lim) {
if (u == t || !lim) {
cost += lim * dis[t];
return lim;
}
vis[u] = 1;
int flow = 0;
for (int &i = cur[u]; i; i = e[i].pre) {
int v = e[i].v, w = e[i].w;
if (!vis[v] && w && dis[v] == dis[u] + e[i].c) {
int tmp = dfs(v, std::min(w, lim));
if (!tmp) dis[v] = kInf;
e[i].w -= tmp, e[i ^ 1].w += tmp;
lim -= tmp, flow += tmp;
if (!lim) break;
}
}
vis[u] = 0;
return flow;
}

void solve() {
for (; spfa(); dfs(s, kInf)) {}
}
} // namespace Dinic

void getmatch() {
int s = 2 * n + 1, t = 2 * n + 2;
Dinic::init(t, s, t);
for (int i = 1; i <= n; ++i) {
Dinic::add(s, i, 1, 0), Dinic::add(i + n, t, 1, 0);
for (int j = 1; j <= n; ++j)
Dinic::add(i, j + n, 1, abs(i - j) + abs(p[i] - q[j]));
}
Dinic::solve();
for (int i = 1; i <= n; ++i) {
match1[i] = 0;
for (int j = 1; j <= n; ++j)
if (!Dinic::e[Dinic::eid[i][j + n]].w)
assert(!match1[i]), match1[i] = j;
match2[p[i]] = q[match1[i]];
}
}

void work(int x, int y) {
vec.emplace_back(x, y), std::swap(p[x], p[y]);
}

void dickdreamer() {
std::cin >> n;
for (int i = 1; i <= n; ++i) std::cin >> p[i];
for (int i = 1; i <= n; ++i) std::cin >> q[i];
getmatch();
vec.clear();
{ // 把 x 变对
for (int i = 1; i <= n; ++i) {
int pos = 0;
while (true) {
for (int j = 1; j <= n; ++j)
if (match1[j] == i)
pos = j;
if (pos == i) break;
for (int j = i; j < pos; ++j) {
if (match1[j] >= pos) {
work(pos, j), std::swap(match1[pos], match1[j]);
break;
}
}
}
}
}
{ // 把 y 变对
static int pp[kMaxN];
for (int i = 1; i <= n; ++i) pp[p[i]] = i;
for (int i = 1; i <= n; ++i) {
int pos = 0;
while (true) {
for (int j = 1; j <= n; ++j)
if (match2[j] == i)
pos = j;
if (pos == i) break;
for (int j = i; j < pos; ++j) {
if (match2[j] >= pos) {
work(pp[pos], pp[j]), std::swap(match2[pos], match2[j]), std::swap(pp[pos], pp[j]);
break;
}
}
}
}
}
// for (int i = 1; i <= n; ++i) std::cerr << p[i] << " \n"[i == n];
std::cout << vec.size() << '\n';
for (auto [x, y] : vec) std::cout << x << ' ' << y << '\n';
}

int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

CF2062G Permutation Factory 题解
https://sobaliuziao.github.io/2025/08/12/post/6077355a.html
作者
Egg_laying_master
发布于
2025年8月12日
许可协议