【模板】归并排序、逆序对

posted on 2021-07-10 20:33:18 | under 题解 | source

非常经典的求逆序对个数问题。An inversion is a pair of indices \((i,j)\) such that \(i<j\) and \(c_i>c_j\).

我们可以使用归并排序求逆序对。

首先来学一下归并排序。它首先把数组一分为二,分别进行归并排序,然后把两个排好序的数组合并成一个有序的大数组,最后合并出来的数组就是有序的。这样递并一层一层向上合的算法,其时间复杂度是 \(O(n\log n)\)。给一个代码实现:

点击查看代码
int a[100010],tp[100010];
void mergesort(int l,int r){
    if(l>=r) return ;
    //将数组一分之二,分别进行归并排序:
    int mid=(l+r)>>1;
    mergesort(l,mid),mergesort(mid+1,r);
    //把两个排好序的数组合并成一个大数组:
    //具体实现方法是用一个数组 tp 存储合并出的数组,
    //最后把 tp 数组放回 a 数组。
	int i=l,j=mid+1,n1=mid,n2=r,cnt=l;
	while(i<=n1&&j<=n2){
		if(a[i]<=a[j]) tp[cnt++]=a[i++];
		else tp[cnt++]=a[j++];
    }
    //把剩余的元素放进去,两个 while 一定只能执行一个
	while(i<=n1) tp[cnt++]=a[i++];
	while(j<=n2) tp[cnt++]=a[j++];
	for(int k=l;k<=r;k++) a[k]=tp[k];
	return ;
}

那如何使用归并排序求逆序对个数呢?改一行代码就行了:

else ans+=n1-i+1,tp[cnt++]=a[j++];

为什么?首先 \(i<j\)\(a_i>a_j\),这个没问题。我们想一下, \(a_{l\cdots mid}\)\(a_{mid+1\cdots r}\) 这两个小数组是有序的,对吧?它们都是递增的,\(\forall i\in[l,mid-1] \; a_i\) 一定 \(<a_{i+1}\),是吗?那,当前这个 \(a_i\) 已经 \(>a_j\) 了,对于更大的 \(a_{i+1},a_{i+2},\cdots\),它们也 \(>a_j\) 吗?答案是肯定的。换一种说法,\(a_{i\cdots mid}>a_j\),你看,这 \(mid-i+1\) 个逆序对不就出现了吗?如果你还不明白,看 link。了解这些后,代码就好写了。注意 \(\max ans=\frac{10^5\times (10^5-1)}{2}\) 会爆 int,开 long long。这里给出我的实现板子:

点击查看代码
#include <iostream>
using namespace std;
typedef long long LL;
int t[1000010];
LL revp(int a[],int l,int r){
// 逆序对 <- inversion
    if(l>=r) return 0;
    int mid=l+r>>1;
    LL ans=revp(a,l,mid)+revp(a,mid+1,r);
    int i=l,j=mid+1,n1=mid,n2=r,cnt=l;
    while(i<=n1&&j<=n2){
        if(a[i]<=a[j]) t[cnt++]=a[i++];
        else ans+=n1-i+1,t[cnt++]=a[j++];
    }
    while(i<=n1) t[cnt++]=a[i++];
    while(j<=n2) t[cnt++]=a[j++];
    for(int k=l;k<=r;k++) a[k]=t[k];
    return ans;
}
int n,a[1000010];
int main(){
    while(cin>>n){
        for(int i=1;i<=n;i++) cin>>a[i];
        cout<<revp(a,1,n)<<endl;
    }
    return 0;
}

练习:


LL inversion(int l,int r){
	if(l==r) return 0;
	int mid=(l+r)>>1;
	LL ans=inversion(l,mid)+inversion(mid+1,r);
	static int tmp[1<<20]; int cnt=l;
	for(int i=l,j=mid+1;i<=mid||j<=r;){
		if(i<=mid&&(j>r||a[i]<=a[j])) tmp[cnt++]=a[i++];
		else ans+=mid-i+1,tmp[cnt++]=a[j++];
	}
	for(int i=l;i<=r;i++) a[i]=tmp[i];
	return ans;
}
posted @ 2022-11-10 09:20  caijianhong  阅读(16)  评论(0编辑  收藏  举报