求逆序对经常使用的两种算法 ----归并排 & 树状数组


网上看了一些归并排求逆序对的文章,又看了一些树状数组的,以为本身也写一篇试试看吧,而后本文大致也就讲个思路(没有例题),可是仍是会有个程序框架的
好了下面是正文算法


  1. 归并排求逆序对
  2. 树状数组求逆序对

1、归并排求逆序对

 

舒适提示:阅读这段内容须要的知识点:归并排序数组

— 首先的话,归并排序你们应该都知道的吧?归并排是利用分治的思想,先分后和,分到左右区间相等或相交时在返回上一层进行两个有序小数组交错插入排序,造成一个有序数组,而后层层返回排好序的数组,做为新的小数组插入大数组排序,这就是一个n log n的排序算法(带 log 的算法通常都算是比较快的,只要常数不过大)。而后仍是不懂的同窗能够百度,这里不细讲了。另外提一提,实在是不会用归并排的话冒泡也是同样能够求逆序对的,累加的话就变成了判断到须要交换时进行,但冒泡的复杂度高了点,是 n^2 了,提交 后会爆几个点就不知道了,得看具体题目和数据。(反正数据通常不会水到让你满分【斜眼笑ing】)框架

— 其次的话,用归并排求逆序对无非也就是在插入的过程当中将 逆序数 ans 累加,而后也没什么不一样的了,只要记得归并排模板的话基本也是码的出来的。(我的感受归并排求逆序队仍是挺清晰的,由于这样基本就是套套模板不用想太多)ui

模板以下,但请别直接复制粘贴,好歹本身打一遍spa

int n,ans;
const int mod=99999997;
int f[100005],g[100005];

void merge_sort(int l,int r)
{
    if(l>=r)  //若是说l、r交错的话直接return无论
        return ;

    int mid=(l+r)>>1;   //以l、r的中点为界向下分支排序
    merge_sort(l,mid);
    merge_sort(mid+1,r);

    int i=l,j=mid+1,k=l;
    while(i<=mid && j<=r)      //保证两个小的数组不超边界
    {
        if(f[i]<f[j])
            g[k++]=f[i++];
        else
        {                //大概要在模板上作修改的就是这块了,用ans把逆序对累加
            ans=(ans+mid-i+1)%mod;    //若是题目中有取余就%mod
            g[k++]=f[j++];
        }
    }
    // 而后把剩下的数直接插入到大的数组末尾(但不会对ans进行累加操做)
    while(i<=mid)
        g[k++]=f[i++];
    while(j<=r)
        g[k++]=f[j++];
    for(int i=l;i<=r;++i)    //g数组只是一个中间量,用完就丢了,f才是要排序的数组
        f[i]=g[i];

}

二次分析

–而后我以为还得解释一下为何ans在j数组(即第二个小数组)中的值插入到达数组的时候才累加。试想,逆序对就是大的数字在前面,小的数字在后面,每次发现一组这样的数字对那么整个数组中的逆序对数量就能够+1了。
如:1 2 6 8 和 3 5 7 9 ,初始i指向1,j指向3,k指向8,l指向9,ans=0
在第一次比较时,1<3,则1插入进大数组,i++,ans不变
第二次比较式,i指向了2, 2<3,则2插入进大数组,仍是i++,ans不变
第三次,i指向6,6>3,3入大数组,j++,ans+=2 。
这里就是重点了,3小于6,则3也必定小于6后面的数,而且能够和这些数(共两个)分别对应造成n个逆序对(n为k-i+1,即6和6的后面总共还剩多少个数)
第四次也同样,是j++,ans+=2,此时ans为4,原理同上,再也不解释
而后就是继续向大数组队尾插入数了,咱们发现直到 i 数组为空时(全被插入完毕了),j 数组仍有剩余,那么就将 j 数组直接插入进大数组,但ans不进行累加(由于此时 i 数组空了,没法与 j 数组中剩下的数造成逆序对)调试

呼~这样总该解释的差很少了,同志们自个儿好好消化消化吧。code

2、树状数组求逆序对

blog

舒适提示:阅读本段须要具有的知识点:树状数组的基本操做(update、getsum、lowbit之类的)排序

–首先的话,树状数组我也不来讲这么详细了,许多细节方面(如 getsum 时x为何要减去一个lowbit(x)了之类的)的理解就麻烦请本身思考得出或是去问百度了。图片

–其次的话,树状数组其实就是代码短一点(短一点就好码一点,好码一点就好调试一点,好调试一点就不容易出错一点),看着舒服吧。
而后我就不啰嗦了,直接上代码吧。

int lowbit(int x)       //lowbit求最末尾的1所在的位置
{
    return x&(-x);
}

void update(int x,int k)    //update等会儿讲
{
    for(;x<=n;x+=lowbit(x))
        g[x]+=k;
}

long long getsum(int x)     //getsum的话。。。也等会儿讲
{
    long long res=0;
    for(;x;x-=lowbit(x))    //一直跳向比x小的数,如7->6->4->0(结束)
                            //或是6->4->0(结束)
        res+=g[x];
    return res;
}

void BIT()      //这个BIT啊,我看书的时候也不知道是什么鬼,
//而后才发现原来是树状数组英文名(Binary Indexed Trees)的缩写
{
    for(int i=1;i<=n;++i)
    {
        update(f[i],1);
        ans=(ans+i-getsum(f[i]))%mod;
    }
}

另外提一点,不要看着这个代码好像行数不少,码一遍以后会发现真的很短

而后讲讲update和getsum吧(主要是给学过的人讲,谈谈个人理解)

上图!

这里写图片描述
在这里的话,你能够认为每一个三角形的顶端都是一个BOSS,一旦他们的下属出现了以后,下属会先+1,再逐级向上汇报(也就是说有小三角形的话就先向小三角形上的BOSS先汇报,而后再由这个小的BOSS向更大的BOSS汇报,直到顶层), 这样的话咱们最后就能够清晰地获得一个实时更新的树状数组,每一个g中所存的就是它以及它的下属目前已出现的个数

这里写图片描述

这里的话 6 在getsum的时候路径为6->4->0(结束),获得的 res 为 1,即已出现的数字中,小于等于6的数字只有一个

这里写图片描述

那么上面演示的是有逆序对的状况,一样的,你也能够自行演示一下 先 2 后 6的状况,这时候你会发现 ans 并无累加,即没有逆序对的状况……
总之,仍是要熟知树状数组操做里的含义吧。

好了,心血来潮写的一篇博客终于搞定了。(大概花了两个多小时的样子,是否是蒟蒻?) 而后若是说有哪里我讲的不对的话,欢迎各位 神(da)犇(lao)在评论区里喷我。_ (:зゝ∠) _ bye bye(下次见)! 1000010 1011001 1000101————!(一串ASCII码)

相关文章
相关标签/搜索