归并排序的应用

归并排序:对于一段数组先分两半,各自进行归并排序,再将两半部分内容有序地合并起来,此时只需各遍历一次即可,采用递归实现。递归的终结点是数组中只有一个数时则不需要进行上述操作。具体参考归并排序具体细节,这里不多描述。
先给出代码模板,在来看应用,其实除了排序,归并排序解决的题目大都可以用树状数组或者线段树解决,这个以后在介绍,模板如下:

void merge_sort(int nums[],int l,int r,int tmp[]){
    if(l>=r) return;
    int mid=l+r>>1;//移位操作,相当于除以2
    merge_sort(nums,l,mid,tmp);
    merge_sort(nums,mid+1,r,tmp);
    int k=0,i=l,j=mid+1;
    while(i<=mid&&j<=r){
        if(nums[i]<=nums[j]) tmp[k++]=nums[i++];
        else tmp[k++]=nums[j++];
    }
    while(i<=mid) tmp[k++]=nums[i++];
    while(j<=r) tmp[k++]=nums[j++];
    for(i=l,j=0;i<=r;i++,j++) nums[i]=tmp[j];
}

归并排序最经典问题就是逆序对问题,关于逆序对定义

逆序对相关问题

关于逆序对问题,可用归并排序、树状数组等求解。若一个数组里元素各不相同,求每次交换相邻两个数,最少需要多少次交换才能使数组有序 等价于求逆序对的数量,且每一个元素移动的次数等于这个数前面大于他的加上这个数后面小于他的

1.逆序对的数量

给定一个长度为 n 的整数数列,请你计算数列中的逆序对的数量。

逆序对的定义如下:对于数列的第 i 个和第 j 个元素,如果满足 i < j 且 a [ i ] > a [ j ] ,则其为一个逆序对;否则不是 逆序对的定义如下:对于数列的第 i 个和第 j 个元素,如果满足 i<j且 a[i]>a[j],则其为一个逆序对;否则不是 逆序对的定义如下:对于数列的第i个和第j个元素,如果满足i<ja[i]>a[j],则其为一个逆序对;否则不是

输入格式

第一行包含整数 n,表示数列的长度。

第二行包含 n 个整数,表示整个数列。

输出格式

输出一个整数,表示逆序对的个数。

数据范围

1 ≤ n ≤ 100000 1≤n≤100000 1n100000
数列中的元素的取值范围 [ 1 , 1 0 9 ] 。 数列中的元素的取值范围 [1,10^9]。 数列中的元素的取值范围[1,109]

输入样例

6
2 3 4 5 6 1

输出样例

5
#include<iostream>
using namespace std;
const int N=1e5+10;
int n;
long long ans;
int a[N],temp[N];
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=0;
    while(i<=mid&&j<=r){
        if(a[i]<=a[j]) temp[k++]=a[i++];
        //前面比自己大的加上去即可
        //归并的最后一层两边各自有序,由mid分割,所以ans+=mid-i+1;
        else temp[k++]=a[j++],ans+=mid-i+1;
        
    }
    while(i<=mid)temp[k++]=a[i++];
    while(j<=r) temp[k++]=a[j++];
    for(int i=l,j=0;i<=r;i++,j++) a[i]=temp[j];
}
int main(){
    cin>>n;
    for(int i=0;i<n;i++) cin>>a[i];
    merge_sort(0,n-1);
    cout<<ans;
    
    return 0;
}

2.最少交换次数

给定一个 1∼N 的随机排列,要求一次只能交换相邻两个数,那么最少需要交换多少次才可以使数列按照从小到大排列呢?

请你求出一个待排序序列的最少交换次数和对应的逆序数列。

逆序数列:给定 n 个数 1,2,…,n 的一个排列 a 1 a 2 … a n a_1a_2…a_n a1a2an,令 b i b_i bi是数 i 在此排列中的逆序数,换句话说, b i bi bi 等于该排列中先于 i 又大于 i 的那些数的个数。数列 b 1 b 2 … b n b_1b_2…b_n b1b2bn 称为排列 a 1 a 2 … a n a_1a_2…a_n a1a2an 的逆序数列(inversion sequence)。

输入格式

第一行一个整数 N。

第二行一个 1∼N的排列。

输出格式

第一行输出逆序数列,数之间用空格隔开。

第二行输出最少交换次数。

数据范围

1≤N≤1000

输入样例

8
4 8 2 7 5 6 1 3

输出样例

6 2 5 0 2 2 1 0
18
//最小交换次数其实就是求逆序对的数量
#include<iostream>
using namespace std;
const int N=1010;
int a[N],temp[N];
int n;
long long ans;
int ansl[N];
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=0;
    while(i<=mid&&j<=r){
        if(a[i]<=a[j]) temp[k++]=a[i++];
        else temp[k++]=a[j++],ans+=mid-i+1,ansl[a[j-1]]+=mid-i+1;
    }
    while(i<=mid) temp[k++]=a[i++];
    while(j<=r) temp[k++]=a[j++];
    for(int i=l,j=0;i<=r;i++,j++) a[i]=temp[j];
}
int main(){
    cin>>n;
    for(int i=0;i<n;i++) cin>>a[i];
    merge_sort(0,n-1);
    for(int i=1;i<=n;i++) cout<<ansl[i]<<" ";
    cout<<endl;
    cout<<ans;
    return 0;
}

3.小朋友排队

n 个小朋友站成一排。

现在要把他们按身高从低到高的顺序排列,但是每次只能交换位置相邻的两个小朋友。

每个小朋友都有一个不高兴的程度。

开始的时候,所有小朋友的不高兴程度都是 0。

如果某个小朋友第一次被要求交换,则他的不高兴程度增加 1,如果第二次要求他交换,则他的不高兴程度增加 2(即不高兴程度为 3),依次类推。当要求某个小朋友第 k 次交换时,他的不高兴程度增加 k。

请问,要让所有小朋友按从低到高排队,他们的不高兴程度之和最小是多少。

如果有两个小朋友身高一样,则他们谁站在谁前面是没有关系的。

输入格式

输入的第一行包含一个整数 n,表示小朋友的个数。

第二行包含 n 个整数 H1,H2,…,Hn分别表示每个小朋友的身高。

输出格式

输出一行,包含一个整数,表示小朋友的不高兴程度和的最小值。

数据范围

1≤n≤100000
0≤Hi≤1000000

输入样例

3
3 2 1

输出样例

9

样例解释

首先交换身高为3和2的小朋友,再交换身高为3和1的小朋友,再交换身高为2和1的小朋友,每个小朋友的不高兴程度都是3,总和为9。

//每个小朋友的移动次数等于前面比他高的和后面比他低的之和
//使用结构体记录身高和编号
//哈希表记录每个小朋友需要移动的次数
#include<iostream>
using namespace std;
#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
typedef pair<int,int> PII;
const int N=1e5+10;
PII a[N],temp[N];//使用结构体储存编号防止出现身高相同情况导致答案变多
int n;
long long ans;
int ansl[N];
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=0;
    while(i<=mid&&j<=r){
        if(a[i]<=a[j]){
           temp[k++]=a[i++];
           ansl[a[i-1].second]+=j-mid-1;//a[j]前面比a[j]小的
        } 
        else {
            temp[k++]=a[j++],ansl[a[j-1].second]+=mid-i+1;//a[j]前面比a[j]大的
            
        }
    }
    while(i<=mid) {
        temp[k++]=a[i++];
        ansl[a[i-1].second]+=j-mid-1;//剩下的a[i]都比a[j]大
    }
    while(j<=r) temp[k++]=a[j++];
    for(int i=l,j=0;i<=r;i++,j++) a[i]=temp[j];
}
int main(){
    IOS;
    cin>>n;
    for(int i=0;i<n;i++) {
        cin>>a[i].first;
        a[i].second=i;
    }
    merge_sort(0,n-1);
    for(int i=0;i<n;i++){
        int t=ansl[a[i].second];
        ans+=(long long)t*(t+1)/2;
        cout<<t<<" ";
    }
    cout<<ans;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值