浅谈如何求逆序对

浅谈求逆序对的两种方式

本篇博客讲解一下信息学奥林匹克竞赛中一种小技巧(我也不知道这是算法还是技巧)——求逆序对


逆序对的概念

先放一波啥是逆序对...

对于一个数列\(a\),假如\(a[i]>a[j]\)并且\(i<j\),那么这个\(a[i],a[j]\)就叫做这个数列的一个逆序对。

简单理解一下:假如本来这个数列是单调递增的,突然出来了一对不和谐的,它非要皮一下,两个数调换一下位置。那么这个不和谐的数对就叫做逆序对。


归并排序求逆序对

归并排序是求逆序对的一个常见并好用的手段。

如果又小可爱不太了解归并排序,那么请翻阅我的这篇博客:

归并排序详解

其实这篇博客已经讲了咋用归并排序求逆序对...就是我下面讲的那些(Ctrl CV大法)

首先来放结论:如果在归并排序过程中,出现\(a[i]>a[j]\),那么就会产生\(mid-i+1\)个逆序对。

很奇妙吧?下面来讲证明:

因为我们做归并排序是用到了分治的思想,最后的操作其实就是递归回溯,从小到大地合并,所以这个时候,我们的两个子序列(即\(l-mid\)\(mid+1-r\)其实都是已经排好序的),这个时候,出现了一个不和谐的\(a[i]\),说明从这个数一直到\(a[mid]\)的所有数都是不和谐的。我们直接累加就好。

代码只需要在模板归并排序的基础上再加一行即可:(已经用\(Attention\)标明)

void merge_sort(int l,int r)
{
    if(l==r)
        return;
    int mid=(l+r)>>1;
    merge_sort(l,mid);
    merge_sort(mid+1,r);
    int i=l,j=mid+1,k=l;
    while(i<=mid && j<=r)
    {
        if(a[i]<=a[j]) 
            b[k++]=a[i++];
        else
        {
            b[k++]=a[j++];
            ans+=mid-i+1;//Attention
        }
    }
    while(i<=mid)
        b[k++]=a[i++];
    while(j<=r)
        b[k++]=a[j++];
    for(int p=l;p<=r;p++)
        a[p]=b[p],b[p]=0;
}

树状数组求逆序对

使用树状数组求逆序对的想法可能更加的抽象一些。

关于树状数组,如果有需要补习的小伙伴,推荐自己的这篇博客:

树状数组知识点详解

首先,我们需要明白,为什么逆序对可以使用树状数组来求:其实,根据逆序对的定义,我们会发现:其实求解逆序对的过程就是在找一个序列中,在一个数的前面有多少比它大的数。那么,这种区间统计的题,完全可以使用树状数组这种数据结构来解决。

树状数组的功能就是单点修改,区间查询。那么,我们开一个树状数组

(注意:这里的数据可能需要进行离散化,当数据量过大的时候,需要把区间大小离散成元素数量,如有离散化不明白的小伙伴,可以看我的这篇博客:)

浅谈离散化

这个树状数组表示的是在当前元素前且比当前元素大的元素个数。我们从头开始扫,每碰到一个数,就用树状数组的修改操作向上修改,加1。我们查询的时候就可以知道有多少个数比它大,然后用现在已经插入的元素个数减去查询的数量(包括它自己),就可以得出当前逆序对的个数。

模板大致如下:(JDOJ 1927 求逆序对

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int c[500010],rk[500010],n;
long long ans; 
struct point
{
    int num,val;
}a[500010];
inline bool cmp(point q,point w)
{
    if(q.val==w.val)
        return q.num<w.num;
    return q.val<w.val;
}
inline void fix(int p,int d)
{
    for(;p<=n;p+=p&-p)
        c[p]+=d; 
}
inline int getsum(int x)
{
    int sum=0;
    for(;x;x-=x&-x)
        sum+=c[x];
    return sum;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i].val),a[i].num=i;
    sort(a+1,a+1+n,cmp);
    for(int i=1;i<=n;i++)
        rk[a[i].num]=i;
    for(int i=1;i<=n;i++)
    {
        fix(rk[i],1);
        ans+=i-getsum(rk[i]);
    }
    printf("%lld",ans);
    return 0;
}
posted @ 2019-10-11 19:23  Seaway-Fu  阅读(815)  评论(0编辑  收藏  举报