归并排序求逆序对

啰嗦大军再次来袭……

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;

const ll maxn=100000+10;
ll a[maxn],b[maxn];
ll n,total;

void msort(ll l,ll r)
{
    ll mid=l + r >> 1;
    if(l<r) //还没有分到最简单的状态(l=r),没有分到底,也就是剩一个元素的时候 
    {//如果包含l=r的话,会无限循环,且l=r是最简单的状态,是停止递归的条件 ,此时开始从最后一个递归调用返回 --->from baike 
        msort(l,mid); 
        msort(mid+1,r); //这两个递归可以当做是处理 寻找范围 的一种方法 ,按顺序计算出下一步要求哪一个区间的逆序对 
    } 

    //每一部分都会被分成两部分考虑到,不用担心逆序对个数的遗漏 如:
    //3 2 1 4 6 5被分为 3 2 1 和 4 6 5 两部分,同样的,3 2 1 和 4 6 5 也会各自被分为3 、2 1 和 4 、6 5两部分,
    //数组以1开头的话,则是被分为 3 2 、1 和 4 6 、5两部分 
    //以此类推 ,3 2 1 和 4 6 5这两部分内部的逆序对也会被找出,不会产生遗漏  

    ll i=l,k=l,j=mid+1;//分成两部分 
    while(i<=mid&&j<=r)
    {
        if(a[i]<=a[j]) b[k++]=a[i++];//a中的当前区间处理出的顺序(每次存小的那个+下面的while循环中处理的)依然存在b的相应区间
                                     //(保证未被操作(未被更新)的数不受影响)                         
        //b中存的是两部分中较小的那个,a[i]小,存入b,i++,a[j]与上一部分的下一个(i+1)比较 
        else
        {
            total+=mid-i+1;//i后面的(i~mid)+i自己(i本身) //后面一部分的元素小,找到一个“前面大于后面的”,逆序对个数增加  
            b[k++]=a[j++];//记录较小的 
        }
    }
    while(i<=mid) b[k++]=a[i++];//j这边的已经放完了,i那边的还有,说明i这边的比较大,把剩下的放进去就好  ① 
    while(j<=r) b[k++]=a[j++];  //同理 ②  
    for(ll i=l;i<=r;i++)a[i]=b[i]; //把排好序的b中的值再赋回a中  (//只更新改的这一部分 ) 
    //把排好序(可能是部分排好序--->通过while把逆序对中小的存到大的之前,
    //以前找到的逆序对消失,不会产生重复查找的事情(一个逆序对被多次找出并记录的情况不会发生。))的b中的值再赋回a中,再对a 
    //进行查找,重复上述操作   
    //另外,①与②只会出现一个(只会有一个满足/成立) 
}

int main()
{
    scanf("%d",&n);
    for(ll i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
    }
    msort(1,n);
    printf("%lld\n",total);

    return 0;
} 

/*
重点注意:why total+=mid-i+1
以4 5 1 6 2 3为例 
我们先分析递归调用的过程,每次一分为二:
4 5 1①--->4 5②,1--->到了4,5,1递归就停止了... 
6 2 3③--->6 2,④1--->到了6,2,1递归就停止了...

递归停止后, 开始从最后一个递归调用返回④--->③--->②--->① 所以是先处理有半部分区间,再处理左半部分区间,也就是说,在处理 4 5 1 6 2 3
这个大区间时,左右两个子区间都是有序的,而且是从小到大排序的,所以如果找到一个逆序对(一定是整个区间的逆序对,所有子区间的逆序对 
已经处理完了,并且小的在前,大的在后,也就是上面提到的“消失了”,最后才处理最大的原来的那个区间)因为大区间是最后处理的,
而且处理前小区间的逆序对已找出,不会有遗漏,而且两个小区间还有序,所以上面提到的 “一定是整个区间的逆序对”的意思就是说,如果有逆序对,
也就是前一个>后一个,那么这个“前一个”一定在左区间,也就是前面的那个区间,这个“后一个”一定在右区间,也就是后面那个区间
那便可以解释 total+=mid-i+1的含义:
在大区间未处理之前,我们的原序列现在为:1 4 5 | 2 3 6,我们可以搜到4和2构成了一对逆序对,又因为每个区间都是递增的,那么,4所在的
那个区间中,在4之后的数(这里只有5),与2肯定也能形成一堆逆序对,毕竟都比4大了,肯定比2大,所以total所加的值,也就是新找到的
逆序对的个数是包括4在内(+1)的4后面所有数的个数的总和(mid-i(i为4的坐标,mid是左区间的右边界)),所以就能得出mid-i+1来了,所以:
total+=mid-i+1 

另外,各个小区间的处理方法与大区间相同,只不过是早处理一点,所以说,total+=mid-i+1也同样适用于各个小区间 

*/

有错误,请指出哟(^U^)ノ~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值