Article Index

CSES 3140 Inversion Sorting 做题笔记

Published on

题意

有一个未知的排列 $a$,其长度为 $n$,你通过进行不超过 $4n$ 次交互将排列排序。

  • 给出 $l, r$,将 $a_l, a_{l + 1}, \ldots, a_r$ 翻转,即变成 $a_r, a_{r - 1}, \ldots, a_l$。操作完成后返回当前数组逆序对个数。

题解

考虑翻转 $\left[l, r\right]$ 对全局逆序对的影响。设:

  • $v_1$ 表示 $\left[1, l - 1\right]$ 中的逆序对数量。
  • $v_2$ 表示 $\left[l, r\right]$ 中的逆序对数量。
  • $v_3$ 表示 $\left[r + 1, n\right]$ 中的逆序对数量。
  • $v_4$ 表示左端点在 $\left[1, l - 1\right]$,右端点在 $\left[l, r\right]$ 中的逆序对数量。
  • $v_5$ 表示左端点在 $\left[1, l - 1\right]$,右端点在 $\left[r + 1, n\right]$ 中的逆序对数量。
  • $v_6$ 表示左端点在 $\left[l, r\right]$,右端点在 $\left[r + 1, n\right]$ 中的逆序对数量。

我们发现,变动的变量只有 $v_2$。新的 ${v_2}' = \binom{r - l + 1}2 - v_2$。

而当前逆序对数与原来的逆序对数之差 $cur - ori = \binom{r - l + 1}2 - 2 \cdot v_2$,由此我们可以通过两次询问算出 $\left[l, r\right]$ 中的逆序对数量并还原排列。

设 $inv_i$ 表示 $\left[1, i\right]$ 中的逆序对数。
令 $b_i = inv_i - inv_{i - 1}$,表示在 $\left[1, i - 1\right]$ 中,比 $a_i$ 大的数的数量。

得知 $b_i$ 之后,可以从后往前确认每个 $a_i$,然后通过 $n$ 次交换将 $a$ 排序。

总交互次数不超过 $3n$。

代码

struct VOIDENGINE {
  int swp(int l, int r) {
    cout << l + 1 << " " << r + 1 << endl;
    return bin();
  }

  VOIDENGINE([[maybe_unused]] int TEST_NUMBER) {
    int n; bin(n);
    vi o(n);
    vi s(n);
    eu(i, n) {
      s[i] = swp(0, i);
      if (s[i] == 0) return;
      o[i] = swp(0, i);
      if (o[i] == 0) return;
    }
    vi inv(n);
    eu(i, n) inv[i] = (o[i] - s[i] + i * (i + 1) / 2) / 2;
    for (int i = n - 1; i > 0; --i) inv[i] -= inv[i - 1];
    vi a(n); iota(ALL(a), 0);
    set<int> all(ALL(a));
    for (int i = n - 1; i >= 0; --i) {
      auto it = all.rbegin();
      eu(inv[i]) ++it;
      a[i] = *it;
      all.erase(*it);
    }
    eu(i, n) {
      if (a[i] == i) continue;
      eu(j, i + 1, n) {
        if (a[j] != i) continue;
        if (swp(i, j) == 0) return;
        reverse(ALL(a, i, j));
        break;
      }
    }
  }
};

void VOIDSPECTRE([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) {
}

No comments

Post a comment