歸併排序

小蔥同學在聽小學數學老師講了如何用歸併排序求逆序對後,就覺得這東西還不如跑一個線段樹來做。但是這天,小蔥的語文老師告訴小蔥這麼一個問題:我們原來是給定N個數,然後通過歸併排序在合併左右區間的時候,求出了左右區間之間形成了多少個逆序對。那麼如果我們現在換一個問題,現在我們是告訴了你每次合併左右區間的時候產生的逆序對數量,你能不能把原有的序列復現出來呢?關於更多的細節,請參考數據規模與約定的部分。

【輸入格式】

第一行一個整數N 代表數的個數

接下來一行若干個數,代表每次區間合併左右區間的時候所產生的逆序對個數。

【輸出格式】

輸出一行N個數,代表一組合法的方案。你需要保證你輸出的是1—N的排列

【樣例】

3

1 2

【樣例】

3 2 1

【數據規模與約定】

對於40%的數據,\(N \leq 10\)

對於70%的數據,\(N \leq 100\)

對於100%的數據,\(1\leq N \leq 10^5\),保證至少有一組合法解,讀入的數不超過int

關於輸入的格式,可以參考隨題面下發的歸併排序的代碼。這段代碼會根據出入的1—N的排列,按順序產生每次合併是產生的逆序對個數。這些數值便是這個題除了N以外的輸入信息。

Solution

考慮歸併排序的步驟。每次從兩個集合裏找較小的數。如果右邊的比左邊的小,就會產生逆序對。也就是說,當右邊的比左邊的先拿出來的時候,會對答案有貢獻。那麼,加入當前數x會產生y的貢獻,當且僅當左邊的序列裏還有y個數,這時候將x拿出即可。

至於怎麼判斷y,可以這樣:只要當前的逆序對個數比左區間的個數多,就從右區間取數,直到逆序對個數不夠,然後就從左區間取出幾個數,使剩下的數的個數等於剩餘逆序對的個數,然後再從右區間取一個數,剩餘的按左右區間的順序取走就行了。

然後就相當於給所有的數的位置排了序。最後再按照這個順序還原數列就好了

#include <iostream>
#include <cstdio>
using namespace std;
inline long long read() {
  long long x = 0; int f = 0; char c = getchar();
  while (c < '0' || c > '9') f |= c == '-', c = getchar();
  while (c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
  return f ? -x : x;
}

int n, a[100005], b[100005];
inline void bsort(int l, int r) {
  if (l == r) return;
  int m = (l + r) >> 1;
  bsort(l, m); bsort(m + 1, r);
  int p1 = l, p2 = m + 1, p = l;
  int v = read();
  while (v >= m - p1 + 1) {//不斷的從右區間取數
    b[p++] = a[p2++], v -= m - p1 + 1;
  }
  for (int i = p1; i <= m - v; ++i) b[p++] = a[p1++];//從左區間取一些數
  if (p2 <= r) b[p++] = a[p2++];
  while (p1 <= m) b[p++] = a[p1++];
  while (p2 <= r) b[p++] = a[p2++];
  for (int i = l; i <= r; ++i) a[i] = b[i];
}
int main() {
  freopen("bsort.in", "r", stdin);
  freopen("bsort.out", "w", stdout);
  n = read();
  for (int i = 1; i <= n; ++i) a[i] = i;
  bsort(1, n);
  for (int i = 1; i <= n; ++i) b[a[i]] = i;//按照位置排序
  for (int i = 1; i <= n; ++i) printf("%d ", b[i]);
  return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章