1. 程式人生 > 其它 >P7629 [COCI2011-2012#1] SORT 題解

P7629 [COCI2011-2012#1] SORT 題解

sort

這題題意就是給定有 \(n\) 個數的陣列 \(a\),每次將 \(a\) 劃分為多個連續的下降區間,將每個有至少兩個數的區間翻轉。問讓 \(a\) 排好序需要至少多少次翻轉。

\(O(n^2)\) 的做法很顯然,就按題意模擬即可,考慮優化。

假設第一次陣列 \(a\) 劃分成了 \([1,2,\dots,p_1],[p_1+1,p_1+2,\dots,p2],\dots,[p_{s-1}+1,p_{s-2}+1,...,p_s](p_s=n)\),那麼做完第一次輪反轉操作後,每一個區間都是按從小到大排好序的。那麼做第二輪操作時,下降區間就只能跨越第一輪的相鄰區間。比如說:4 2 3 1

,第一輪為 4 2 | 3 1,翻轉之後變成了 2 4 | 1 3,第二次下降區間只有 4 1。然後有個顯而易見的結論就是第二次以後的下降區間長度只能為 \(2\)

那麼問題就轉換為了每次交換相鄰的數,問排好序之後的最小交換次數。然後逆序對做就行了。

程式碼:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N = 1e5 + 5;

int n;
LL ans;
int a[N], cut[N];

class BIT {
  public:
    void upd (int x, int v) {
      for (; x <= n; x += x & -x)
        c[x] += v;
    }
    LL qry (int x) {
      LL ret = 0;
      for (; x; x -= x & -x)
        ret += c[x];
      return ret; 
    }
  private:
    LL c[N];
} t ;

bool check () {
  for (int i = 1; i <= n; ++i)
    if (a[i] != i) return false;
  return true;
}

void solve () {
  int cnt = 0;
  for (int i = 1; i <= n; ++i)
    if (a[i] < a[i + 1])
      cut[++cnt] = i;
  for (int i = 1; i <= cnt; ++i)
    if (cut[i] - cut[i - 1] != 1) {
      reverse(a + cut[i - 1] + 1, a + cut[i] + 1);
      ++ans;
    }
  for (int i = 1; i <= n; ++i) {
    ans += i - 1 - t.qry(a[i]);
    t.upd(a[i], 1);
  }
  cout << ans << endl;
}

int main() {
  // freopen("sort.in", "r", stdin);
  // freopen("sort.out", "w", stdout);
  cin >> n;
  for (int i = 1; i <= n; ++i)
    cin >> a[i];
  a[n + 1] = n + 1;
  solve();
  cerr << clock() * 1.0 / CLOCKS_PER_SEC << 's' << endl;
  return 0;
}