经典题:逆序对 归并和树状数组两种解法

108 阅读3分钟

逆序对的定义:序列中 i<j且 ai>aj 的有序对。

首先看一下样例:

A:5 4 2 6 3 1

那么数组A存在的逆序对分别是:

5 4
5 2 
5 3
5 1
4 2
4 3
4 1
2 1
6 3
6 1
3 1

ans=11

我们通过打表可以发现,我们去固定一个左指针,右指针去遍历整个数组,寻找满足条件的逆序对的另一半,这也是最朴素的做法:

#include<bits/stdc++.h>
using namespace std;
#define int long long 

int ans; 
signed main()
{
	cin.tie(nullptr)->sync_with_stdio(false);
	int n;cin>>n;
	vector<int>a(n);
	for(int i=0;i<n;i++)
	{
		cin>>a[i];
	}
    
    for(int i=0;i<n-1;i++)
    {
    	for(int j=i+1;j<n;j++)
    	{
    		if(a[i]>a[j])ans++;
		}
	}
	cout<<ans<<endl;
return 0;	
} 

n的最大范围是5e10,朴素做法两层循环On2O(n^2),一定会超时: image.png

那么排序呢?排成降序,那么a[i]一定大于a[j],这样就不用一个一个去对比大小了,提高效率:

但是实际上排完序之后答案就不对了:

image.png

验证:

排完序之后的数组是:

A:6 5 4 3 2 1

那么数组A中存在的逆序对分别为:

6 5
6 4
6 3
6 2
6 1
5 4
5 3
5 2
5 1
4 3
4 2
4 1
3 2
3 1
2 1

ans=15

正解

一.归并排序

对于逆序对,我们这里采用归并排序分治思想:

比如,对于如下序列B,有多少对逆序对呢?

B:5 6 7 1 3 8

我们首先把它分为两个区间: 因为 5大于1,又因为左区间是升序区间,所以5之后的所有数都大于1

 左指针:i
 左区间:5 6 7
 右区间:1 3 8
 右指针:j
 
 ans=3

因为5大于3,所以5之后的所有数都大于3

 左指针:i
 左区间:5 6 7
 右区间:1 3 8
 右指针:  j
 
 ans=6

5小于8,移动i指针,发现左区间的所有数都小于8

 左指针:    i
 左区间:5 6 7
 右区间:1 3 8
 右指针:    j
 
 ans=6

那么数组B的逆序对个数就是6。

这种方法每个区间只需要遍历 n/2,总共需要遍历n ,分区间可以用二分去分,那么总共时间复杂度就是O(nlog(n)) O(nlog(n))

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define M 500005
int a[M], temp[M], n;
long long ans = 0;

void msort(int l, int r)
{       
  //递归结束条件
	if (l == r)return;
	int mid = l + r >> 1;
	msort(l, mid);      //左区间排序
	msort(mid + 1, r);  //右区间排序

	int i = l, j = mid+1, k = l;

	while (i <= mid && j <= r)  
	{
        //谁小谁给temp
		if (a[i] <= a[j])temp[k++] = a[i++];
		else
		{
			temp[k++] = a[j++];
			ans+=mid - i + 1;   //求逆序对的个数
		}
	}


	//谁还剩,剩下的全部放到temp里面
	while (i <= mid)temp[k++] = a[i++];
	while (j <= r)temp[k++] = a[j++];
        
        //放回原数组
	for (int i = l; i <= r; i++)a[i] = temp[i];
}
signed  main()
{
	cin.tie(nullptr)->sync_with_stdio(false);
	cin >> n;
	for (int i = 1; i <= n; i++)cin >> a[i];
	msort(1, n);
	cout << ans << endl;
	return 0;
}

image.png

树状数组