Description
给定 n,C 和两个排列 a1,a2,…,an 和 b1,b2,…,bn,每次可以用 ∣ai−aj∣+C 的代价交换 ai 和 aj。问将序列从 a 变成 b 的最小代价。
n≤106。
Solution
先连 ai→bi,每次相当于是交换两个点的出边。
交换次数的下界是 n 减置换环数量,∣ai−aj∣ 这个权值的下界是 ∑∣ai−bi∣,容易发现这两个都能取到下界。
考虑怎么证明这件事情。
首先每次交换的两个位置一定满足 bj≤ai≤aj≤bi,且在同一置换环中,否则一定会浪费。
设 prebi=ai,nxtai=bi,考虑找到当前最小的数 x,每次操作 x,直到 nxtx=x。我们相当于是要找到一个 y,使得 x≤y≤prex≤nxty。
不妨设 x 所在的置换环中有 c 个在 [x,prex−1] 之间的数,那么这 c 个数有 c 个出边,而在 [x,prex−1] 的入边最多只有 c−1 个,因为 prex 不在这个范围内。所以一定能找到这样的 y 进行操作。
暴力跳 nxt 找 y 的复杂度是 O(n2),过不了,考虑优化。
这里的技巧是从 x 同时跳 pre 和 nxt 找 y,则找到 y 的次数为 O(环长/2),进一步会发现次数为当前的环分裂后的较小的环长。根据启发式分裂的思想,这个东西的总复杂度就是 O(nlogn)。
时间复杂度:O(nlogn)。
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
| #include <bits/stdc++.h>
#define int int64_t
const int kMaxN = 2e5 + 5;
int n, c, cost; int a[kMaxN], b[kMaxN], unq[kMaxN], pos[kMaxN], pre[kMaxN], nxt[kMaxN]; std::vector<std::pair<int, int>> vec;
void work(int x, int y) { int i = pos[x], j = pos[y]; cost += abs(unq[a[i]] - unq[a[j]]) + c; int a1 = x, b1 = nxt[x], a2 = y, b2 = nxt[y]; std::swap(a[i], a[j]); std::swap(pos[x], pos[y]); nxt[a1] = b2, pre[b2] = a1; nxt[a2] = b1, pre[b1] = a2; vec.emplace_back(i, j); }
void discrete() { std::sort(unq + 1, unq + 1 + n); for (int i = 1; i <= n; ++i) { a[i] = std::lower_bound(unq + 1, unq + 1 + n, a[i]) - unq; b[i] = std::lower_bound(unq + 1, unq + 1 + n, b[i]) - unq; nxt[a[i]] = b[i], pre[b[i]] = a[i]; pos[a[i]] = i; } }
void dickdreamer() { std::cin >> n >> c; cost = 0, vec.clear(); for (int i = 1; i <= n; ++i) { std::cin >> a[i] >> b[i]; unq[i] = a[i]; } discrete(); for (int i = 1; i <= n; ++i) { for (; pre[i] != i;) { for (int x = i, y = i;; x = pre[x], y = nxt[y]) { if (x <= pre[i] && nxt[x] >= pre[i]) { work(pre[i], x); break; } if (y <= pre[i] && nxt[y] >= pre[i]) { work(pre[i], y); break; } } } } std::cout << cost << ' ' << 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(); return 0; }
|