逆序对的数量 做题笔记

逆序对的定义:对于数列的第 i 个和第 j 个元素,如果满足 i < j 且 a[i] > a[j],则其为一个逆序对。
重要的地方在于,一个元素可以不只是在一个逆序对中存在。如果 k > j > i 且 a[i] > a[j] > a[k],那么这里有两个含 a[i] 的逆序对,分别是 (a[i], a[j]) 和 (a[i], a[k]), a[i]是可以使用多次的。

分析问题,这里我们可以使用分治法解决问题。

将序列从中间分开,将逆序对分成三类

两个元素都在左边;
两个元素都在右边;
两个元素一个在左一个在右;

计算逆序对的数量(序列):
1. 递归算左边的;
2. 递归算右边的;
3. 算一个左一个右的;
4. 把他们加到到一起。

这个时候我们注意到一个很重要的性质,左右半边的元素在各自任意调换顺序,是不影响第三步计数的,因此我们可以数完就给它排序。这么做的好处在于,如果序列是有序的,会让第三步计数很容易,这也是为什么最后必须像标准归并一样把新数组tmp的数填回q数组。
如果无序暴力数的话这一步是O(n^2)的。

关于为什么可以用归并解决且本质都是一左一右,y总思想:

先假定归并排序把序列进行排序并返回逆序对的数量。
然后当两个数在左边或者右边的时候就是直接调用归并排序返回。
当出现一个在左,一个在右的时候,就是第三种情况,利用多路归并算法进行求解。
而前面两种由于递归的性质最后都会变成第三种情况。

还要额外注意一个问题就是数据溢出,本题要用long long 类型,因为考虑逆序数对的数量最大情况(数据是从大到小排列的),那么就是n-1+n-2+n-3+...+1,等差数列求和,(n-1)n=O(n^2),n=100000,n^2=10^10.

#include<iostream>
using namespace std;
const int N=100010;
typedef long long LL;
int q[N],tmp[N];
LL mergesort(int q[],int l,int r){
    if(l>=r) return 0;
    int mid=l+r>>1;
    LL res=mergesort(q,l,mid)+mergesort(q,mid+1,r);
    int i=l,j=mid+1,x=q[l+r>>1],k=0;
    while(i<=mid&&j<=r){
        if(q[i]<=q[j]){
            tmp[k++]=q[i++];
        }
        else{
            tmp[k++]=q[j++];
            res+=mid-i+1;
        }
    }
    while(i<=mid) tmp[k++]=q[i++];
    while(j<=r) tmp[k++]=q[j++];
    for(i=l,j=0;i<=r;i++,j++) q[i]=tmp[j];
    return res;
}
int main(){
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++) scanf("%d",&q[i]);
    cout<<mergesort(q,0,n-1)<<endl;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值