QOJ #4424. Babushka and her pierogi 题解

Description

给定 \(n,C\) 和两个排列 \(a_1,a_2,\ldots,a_n\)\(b_1,b_2,\ldots,b_n\),每次可以用 \(|a_i-a_j|+C\) 的代价交换 \(a_i\)\(a_j\)。问将序列从 \(a\) 变成 \(b\) 的最小代价。

\(n\leq 10^6\)

Solution

先连 \(a_i\to b_i\),每次相当于是交换两个点的出边。

交换次数的下界是 \(n\) 减置换环数量,\(|a_i-a_j|\) 这个权值的下界是 \(\sum|a_i-b_i|\),容易发现这两个都能取到下界。

考虑怎么证明这件事情。

首先每次交换的两个位置一定满足 \(b_j\leq a_i\leq a_j\leq b_i\),且在同一置换环中,否则一定会浪费。

\(pre_{b_i}=a_i,nxt_{a_i}=b_i\),考虑找到当前最小的数 \(x\),每次操作 \(x\),直到 \(nxt_x=x\)。我们相当于是要找到一个 \(y\),使得 \(x\leq y\leq pre_x\leq nxt_y\)

不妨设 \(x\) 所在的置换环中有 \(c\) 个在 \([x,pre_x-1]\) 之间的数,那么这 \(c\) 个数有 \(c\) 个出边,而在 \([x,pre_x-1]\) 的入边最多只有 \(c-1\) 个,因为 \(pre_x\) 不在这个范围内。所以一定能找到这样的 \(y\) 进行操作。

暴力跳 \(nxt\)\(y\) 的复杂度是 \(O(n^2)\),过不了,考虑优化。

这里的技巧是从 \(x\) 同时跳 \(pre\)\(nxt\)\(y\),则找到 \(y\) 的次数为 \(O(环长/2)\),进一步会发现次数为当前的环分裂后的较小的环长。根据启发式分裂的思想,这个东西的总复杂度就是 \(O(n\log n)\)

时间复杂度:\(O(n\log n)\)

Code

#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;) {
      // std::cerr << i << ' ' << pre[i] << '\n';
      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();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}
posted @ 2025-08-28 15:42  下蛋爷  阅读(15)  评论(0)    收藏  举报