什么是逆序对
逆序对就是如果 i > j & & a [ i ] < a [ j ] i>j\&\& a[i] <a[j] i>j&&a[i]<a[j],这两个数就是一个逆序对。其实换句话说,找到排在自己前面比自己大的数的个数。
给定一个 n n n个数的序列,求解序列中逆序对的个数 n ≤ 5 × 1 0 5 , a [ i ] ≤ 1 0 9 n \leq 5 \times 10^5,a[i]\leq 10^9 n≤5×105,a[i]≤109。
输入样例
6
5 4 2 6 3 1
输出样例
11
求解逆序对
方法一:暴力求解
使用两个循环去跑。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
#define _for(i, a, b) for (int i=(a); i<=(b); i++)
const int INF = 0x7fffffff;
const int MAXN = 1e6 + 10;
int n, arr[MAXN];
ll res;
int main(){
cin >> n;
for(int i = 1; i <= n; i++){
cin >> arr[i];
}
for(int i = 1; i <= n; i++){
for(int j = 1; j < i; j++){
if(arr[j] > arr[i]) res++;
}
}
cout << res << endl;
return 0;
}
方法二:对并排序解法
首先我们要知道什么是归并排序。然后,我们可以这样思考:如果我们将一个序列排成从小到大的有序序列,那么我们每次划分合并后的左右序列都是从小到大的有序序列,我们只需要记录右区间每个数分别会与左区间产生多少逆序对即可。
如果不理解的话,我们可以看个例子:
首先,我们对输入数组一直对半拆分,直到区间内只有一个元素的时候,一个元素肯定是有序数组,然后我们在依次合并两个有序数组,计算逆序对的个数就发生在合并的过程中,一直重复整个过程,直到整个数组有序。(整个过程大致如下)
相信大家也都看懂了(迷之自信^ _ ^)
由于归并排序没有什么坑,正常的执行并统计即可,注意答案可能会爆 i n t int int,所以我们需要使用 l o n g l o n g long\quad long longlong来存储。时间复杂度和普通的归并排序一样 O ( N l o g 2 N ) O(Nlog_2N) O(Nlog2N)。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
#define _for(i, a, b) for (int i=(a); i<=(b); i++)
const int INF = 0x7fffffff;
const int MAXN = 1e6 + 10;
int n, a[MAXN], b[MAXN];
ll res;
void msort(int l, int r){//归并排序
if(l == r) return ;
int mid = l + (r - l) / 2;//这里是二分的一个坑,如果l,r两个数很大,可能导致l+r出错。
int i = l, j = mid + 1, k = l;
msort(l,mid);
msort(mid+1,r);
while(i <= mid && j <= r){
if(a[i] <= a[j]) b[k++] = a[i++];
else{
b[k++] = a[j++];
res += mid - i + 1; //记录答案
}
}
while(i <= mid){
b[k++] = a[i++];
}
while(j <= r){
b[k++] = a[j++];
}
for(int t = l; t <= r; t++){
a[t] = b[t];
}
}
int main(){
cin >> n;
for(int i = 1; i <= n; i++){
cin >> a[i];
}
msort(1,n);
cout << res << endl;
return 0;
}
方法三:树状数组求解
-
首先,我们要思考一个问题,我们要怎样记录第 i i i个数与 1 … i − 1 1\dots i-1 1…i−1个数构成的逆序对呢?
我们都知道,初始化的树状数组全为 0 0 0,现在我们按照序列从左到右将数值对应的位置加一,表示有一个数出现。因此,在循环到第 i i i个数的时候,前 i − 1 i-1 i−1个数也已经加入树状数组中,树状数组内比 a i a_i ai大的都会与 a i a_i ai构成逆序对,因为它们肯定会比 a i a_i ai出现的早,所以产生的逆序对数量为 i − g e t s u m ( a i ) i-getsum(a_i) i−getsum(ai)。
注意: g e t s u m ( a i ) getsum(a_i) getsum(ai)是在树状数组中询问 1 … a i 1\dots a_i 1…ai项的前缀和。 -
接下来,我们来思考新的问题,如果 a i a_i ai的数值过大,树状数组的内存空间不够怎么办?
我们先来看一个例题:
我们都知道1000 5 6这三个数的逆序对是2
同样的3 1 2这三个数的逆序对也是2
所以说,上面的两个序列是等价的,因为无论3还是1000,都大于第二项和第三项,因此我们知道,我们只需要数据之间的相对大小,只需要满足大于或小于本身,与大多少无关。
这就启发我们对数据进行离散化。先将数据进行排序,再用 1 1 1到 n n n分别对应 n n n个数表示它们的相对大小,对新的序列建树状数组空间就够了。
-
下面,我们思考最后一个问题,相等的元素是否会导致求解错误?
不处理话会出现错误,问题的关键在于是否有和 a i a_i ai相等的元素在 a i a_i ai之前被加入且相对大小标记更大。出现这种情况就会误将两个相等的元素判为逆序对。那么我们要怎样解决呢?其实我们只要将所有与 a i a_i ai相等的元素,先出现的标记更小就可以了。
同时再次注意答案可能会爆 i n t int int,所以我们需要使用 l o n g l o n g long\quad long longlong来存储。 O ( N l o g 2 N ) O(Nlog_2N) O(Nlog2N)。
由于我们不仅要排序,还要建树状数组,所以,树状数组相对归并排序会慢一些。但是当数值域小的时候树状数组会更快,两者均有有点,我们都需掌握。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
#define _for(i, a, b) for (int i=(a); i<=(b); i++)
const int INF = 0x7fffffff;
const int MAXN = 1e6 + 10;
int n, b[MAXN];
struct node{
int val, id;
bool operator < (const node & rhs) const{
return (this->val < rhs.val) || (this->val == rhs.val && this->id < rhs.id);//从小到大排序,数值相同id小的在前 解决第三个问题
}
}arr[MAXN];
inline int lowbit(int x){
return (x & -x);
}
void add(int x, int d){
while(x <= n){
b[x] += d;
x += lowbit(x);
}
}
ll sum(int x){
ll res = 0;
while(x > 0){
res += b[x];
x -= lowbit(x);
}
return res;
}
int main(){
ll res = 0;
cin >> n;
for(int i = 1; i <= n; i++){
cin >> arr[i].val;
arr[i].id = i;
}
sort(arr+1,arr+1+n);//离散化数值,解决第二个问题
for(int i = 1; i <= n; i++){
add(arr[i].id,1);//离散化结果—— 下标等效于数值
res += (i - sum(arr[i].id));//计算逆序对
}
cout << res << endl;
return 0;
}