求解逆序对

什么是逆序对

逆序对就是如果 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 n5×105a[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;
}
方法三:树状数组求解
  1. 首先,我们要思考一个问题,我们要怎样记录第 i i i个数与 1 … i − 1 1\dots i-1 1i1个数构成的逆序对呢?

    我们都知道,初始化的树状数组全为 0 0 0,现在我们按照序列从左到右将数值对应的位置加一,表示有一个数出现。因此,在循环到第 i i i个数的时候,前 i − 1 i-1 i1个数也已经加入树状数组中,树状数组内比 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) igetsum(ai)
    注意: g e t s u m ( a i ) getsum(a_i) getsum(ai)是在树状数组中询问 1 … a i 1\dots a_i 1ai项的前缀和

  2. 接下来,我们来思考新的问题,如果 a i a_i ai的数值过大,树状数组的内存空间不够怎么办?

    我们先来看一个例题:

    我们都知道1000 5 6这三个数的逆序对是2

    同样的3 1 2这三个数的逆序对也是2

    所以说,上面的两个序列是等价的,因为无论3还是1000,都大于第二项和第三项,因此我们知道,我们只需要数据之间的相对大小,只需要满足大于或小于本身,与大多少无关

    这就启发我们对数据进行离散化。先将数据进行排序,再用 1 1 1 n n n分别对应 n n n个数表示它们的相对大小,对新的序列建树状数组空间就够了。

  3. 下面,我们思考最后一个问题,相等的元素是否会导致求解错误?

    不处理话会出现错误,问题的关键在于是否有和 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;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星*湖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值