模板:排序(四)

归并排序

“然而,”路由器说道,“快排的魔法是不稳定的……你的代码最终没有通过啊怎么办?”
“啊?那那……”勇者支支吾吾。
于是,他们只好造访了照相馆,借来了一本魔法书,但是魔法书经过风吹日晒所以代码丢失了,不过他的解释步骤还是很明显的。
“……那么想必看到这里的魔法师一定都会了神奇的桶排序了,我们可以借用这个思想来解释下接下来要讲的魔法……”
“归并排序,O(NlogN)”
“我们首先将整个数据分成两半,将每一半放在一个桶里面,然后对产生的新桶重复这个动作,一直到无法继续分下去(也就是桶里只剩下了一个元素为止)。”
“之后就是惊心动魄的排序环节了。我们将每一个从一个桶里面分裂出来的两个桶称作同类,此时我们所需要做的,就是将同类桶进行排序,合并成原先分裂的桶,然后继续将同类桶排序……”
“恩,没了……”路由器指了一下残叶,“来吧,我们来写代码吧……”
代码如下:

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
int n,a[1000111],t[1000111];
void msort(int x,int y)
{
    if(x==y) return;
    int mid=(x+y)/2;
    msort(x,mid);
    msort(mid+1,y);
    int l=x,r=mid+1,j=x;//l左指针,r右指针,j当前数位 
    while(l<=mid&&r<=y)//比较,落下小的 
    {
        if(a[l]<=a[r]){
            t[j]=a[l];l++;j++;//左指针++,当前数位++ 
        }
        else{
            t[j]=a[r];r++;j++;//右指针++,当前数位++ 
        }
    }
    while(l<=mid){//将剩余没落下的数字按顺序落下 
        t[j]=a[l];l++;j++;
    }
    while(r<=y){
        t[j]=a[r];r++;j++;
    }
    for(int i=x;i<=y;i++)a[i]=t[i];//更新数组为排序后的数组 
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
    }
    msort(1,n);
    for(int i=1;i<=n;i++){
        printf("%d ",a[i]);
    }
    return 0;
}

逆序对问题

“然而,这个魔法的作用却不只是排序,因为排序大部分情况下都是sort,所以我们可以用来解决……”

“逆序对问题”

(OpenJudge 7622)
总时间限制:
1000ms

内存限制:
65536kB
描述
在Internet上的搜索引擎经常需要对信息进行比较,比如可以通过某个人对一些事物的排名来估计他(或她)对各种不同信息的兴趣,从而实现个性化的服务。

对于不同的排名结果可以用逆序来评价它们之间的差异。考虑1,2,…,n的排列i1,i2,…,in,如果其中存在j,k,满足 j < k 且 ij > ik, 那么就称(ij,ik)是这个排列的一个逆序。

一个排列含有逆序的个数称为这个排列的逆序数。例如排列 263451 含有8个逆序(2,1),(6,3),(6,4),(6,5),(6,1),(3,1),(4,1),(5,1),因此该排列的逆序数就是8。显然,由1,2,…,n 构成的所有n!个排列中,最小的逆序数是0,对应的排列就是1,2,…,n;最大的逆序数是n(n-1)/2,对应的排列就是n,(n-1),…,2,1。逆序数越大的排列与原始排列的差异度就越大。
现给定1,2,…,n的一个排列,求它的逆序数。

输入
第一行是一个整数n,表示该排列有n个数(n <= 100000)。
第二行是n个不同的正整数,之间以空格隔开,表示该排列。
输出
输出该排列的逆序数。
样例输入
6
2 6 3 4 5 1
样例输出
8

提示:
1.利用二分归并排序算法(分治);
2.注意结果可能超过int的范围,需要用long long存储。

好的,为什么说归并排序可以解决这个问题呢?还记得归并排序的原理吗?实际上,归并排序已经将所有的区间都给我们划分好了,所以我们只需要求出每个区间的逆序对有几个就可以了。

而且归并排序因为每归并一次都会排好序,所以我们的计算也可以变得更加简单(详情看注释)。
那么附上代码吧,也没什么好讲的:

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
int n,a[1000111],t[1000111];
long long ans = 0;
void msort(int x,int y)
{
    if(x==y) return;
    int mid = (x + y) /2;
    msort(x,mid);
    msort(mid+1,y);
    int l = x, r = mid+1, j = x;
    while(l<=mid && r<=y)
    {
        if(a[l]<=a[r]){
            t[j] = a[l]; j++;l++;
        }
        else{
            t[j] = a[r]; r++;j++; 
            ans += mid-l+1;//在l与mid区间里面每一个数对于r来说都是它的逆序对
        }
    }
    while(l<=mid){
        t[j] = a[l]; j++;l++;
    }
    while(r<=y){
        t[j] = a[r]; r++;j++;
    }
    for(int i=x;i<=y;i++)a[i] = t[i];
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
    }
    msort(1,n);
    printf("%lld",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值