本題沒有什麼難度,一個模板題。之所以要寫這個題解,是因爲我自己第一次提交的時候,竟然沒有 AC。老子立馬懵逼了。後面耐心的分析了一下數據,才發現自己的錯誤在哪裏。透劇一下,掉到數據越界的深坑了。所以記錄一下。
題目
題目鏈接
一本通OJ:http://ybt.ssoier.cn:8088/problem_show.php?pid=1311。
我的OJ:http://47.110.135.197/problem.php?id=4134。
題目描述
給定一個序列 a1 , a2 , … , an,如果存在 i < j 並且 ai > aj,那麼我們稱之爲逆序對,求序列中逆序對的數目。
輸入
第一行爲 n,表示序列長度。
接下來一行中有 n 個元素,第 i 個數表示序列中的第 i 個數。
輸出
一行。所有逆序對總數。
樣例輸入
4
3 2 3 2
樣例輸出
3
數據範圍
1 ≤ N ≤ 10^5,
-10^5 ≤ ai ≤ 10^5。
分析
一個標準求逆序對模板題。
逆序對
設 A 爲一個有 n 個數字的有序集(n > 1),其中所有數字各不相同。如果存在正整數 i, j 使得 1 ≤ i < j ≤ n 而且 A[i] > A[j],則 <A[i], A[j]> 這個有序對稱爲 A 的一個逆序對,也稱作逆序數。
例如,數組(3,1,4,5,2)的逆序對有(3, 1),(3, 2),(4, 2),(5, 2),共 4 個逆序對。
算法思路
求數組的逆序對就是對數組進行排序,在排序過程中記錄逆序的數量即可。這樣我們需要使用穩定排序算法,比如冒泡排序、插入排序和歸併排序。
數據範圍分析
1、從題目中,我們可以知道 n 的範圍是 [1, 10^5],這樣我們就知道所有 O(n^2) 複雜度的排序算法肯定是 TLE,也就是說冒泡排序和插入排序不能使用,只有是要歸併排序。
2、假設最壞的情況,也就是這 10^5 個數據全部是倒序,那麼逆序對應該是 。這個數據什麼意思?我們來回憶一下 C++ 數據類型 unsigned int 的最大範圍是 4,294,967,295,也就是 ,說明 unsigned int 已經容納不下這個數據。太太太坑爹了。
歸併排序求逆序對
這個部分是分析的核心。首先我們用一個樣例數據來說明,使用歸併排序。假設我們有一個數列 [5 4 2 6 3 1],使用歸併排序來看一下有幾個逆序對。
第一步:二分,如下圖所示。逆序對數量 = 0。
第二步:第四層 5 和 4 合併:i=l=1; r=2; mid=(l+r)/2=1; j=mid+1=2; 因爲 5>4,逆序對數量+mid-i+1=1。b數組:[4 5 0 0 0 0],j+1>r;退出;a 數組 [4 5 2 6 3 1]。如下圖所示。
第三步:第四層 6 和 3 合併:i=l=4; r=5; mid=(l+r)/2=4; j=mid+1=5; 因爲 6>3,逆序對數量+mid-i+1=2。b數組:[4 5 0 3 6 0],j+1>r;退出;a 數組 [4 5 2 6 3 1]。如下圖所示。
第四步:第三層 4,5 和 2 合併:i=l=1; r=3; mid=(l+r)/2=2; j=mid+1=3; 因爲 4>2,逆序對數量+mid-i+1=4。b數組:[2 4 5 3 6 0],j+1>r;退出;a 數組 [2 4 5 3 6 1]。如下圖所示。
第五步:第三層 3,6 和 1 合併:i=l=4; r=6; mid=(l+r)/2=5; j=mid+1=6; 因爲 3>1,逆序對數量+mid-i+1=6。b數組:[2 4 5 1 3 6],j+1>r;退出;a 數組 [2 4 5 1 3 6]。如下圖所示。
第六步:第二層 2,4,5 和 1,3,6 合併:i=l=1; r=6; mid=(l+r)/2=3; j=mid+1=4; 因爲 2>1,逆序對數量+mid-i+1=9。b數組:[1 2 4 5 3 6],j+1;對應 2<3,b數組:[1 2 4 5 3 6],i+1;對應 4>3,逆序對數量+mid-i+1=11,b數組:[1 2 3 4 5 6]。j+1;對應 4<6,b數組:[1 2 3 4 5 6];i+1,5<6,b數組:[1 2 3 4 5 6];i+1>mid,退出,a 數組 [1 2 3 4 5 6],有序。如下圖所示。
從上面的分析可以看出,使用歸併排序求逆序對,最核心的是下面這句話
ans+=mid-i+1;
AC 參考代碼
//http://ybt.ssoier.cn:8088/problem_show.php?pid=1311
//求逆序對
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e6+4;
int nums[MAXN] = {};
int tmps[MAXN];
long long ans = 0;
//使用歸併排序
void merge(int l, int mid, int r) {
int i=l;
int j=mid+1;
int k=l;
while (i<=mid && j<=r) {
if (nums[i]>nums[j]) {
tmps[k++]=nums[j++];
ans+=mid-i+1;
} else {
tmps[k++]=nums[i++];
}
}
while (i<=mid) {
tmps[k++]=nums[i++];
}
while (j<=r) {
tmps[k++]=nums[j++];
}
for (i=l; i<=r; i++) {
nums[i]=tmps[i];
}
}
void mergeSort(int l, int r) {
int mid;
if (l < r) {
mid = l+((r-l)>>1);
mergeSort(l, mid);
mergeSort(mid+1, r);
merge(l, mid, r);
}
}
int main() {
int n;
cin >> n;
int i;
for (i=0; i<n; i++) {
cin >> nums[i];
}
mergeSort(0, n-1);
cout << ans << endl;
return 0;
}