归并排序思想的应用:求逆序对

前言

学了这么久的归并排序,感觉没什么卵用。。。。而且STL中有sort这么强大的工具,归并排序早就吃灰了

先回顾下什么是归并排序

要想知道什么是归并排序,先看看什么是归并

举个例子:将两个已经排好序(例如从小到大)的数组合并成一个有序数组,要求时间复杂度O(n)

用two-pointer的思想很好解决,用一个pointer指向第一个数组,另一个指向第二个数组,比较两个pointer所指的元素大小,哪个小九八哪个放进结果数组中,并把刚刚的pointer往后移,,,,,很简单就不再说了,代码如下

基于这种想法,可以得到归并排序的算法

一开始数组是无序的,当分割的两个数组都只有一个元素时,即可排序。此时两个元素的数组就有序,接着四个元素的数组就有序。。。。。依次,可以得到最终的有序数组,显然这种排序算法的时间复杂度为O(logN)

Coding如下:

const int MAX = 5e5+5;

int N;
int a[MAX];

void merge(int l1,int r1,int l2,int r2) {
    int i=l1,j=l2;
    int tmp[MAX],count=0;
    while (i<=r1&&j<=r2) {
        if(a[i]<=a[j]) tmp[count++]=a[i++];
        else {
            tmp[count++]=a[j++];
            res += r1-i+1;
        }
    }
    while (i<=r1) tmp[count++]=a[i++];
    while (j<=r2) tmp[count++]=a[j++];
    for (int k = 0; k < count; ++k) {
        a[l1+k]=tmp[k];
    }
}

void mergeSort(int l,int r){
    if(l>=r) return;
    int mid = (l+r)/2;
    mergeSort(l,mid);
    mergeSort(mid+1,r);
    merge(l,mid,mid+1,r);
}

下面看应用,题目如下

题目描述

猫猫 TOM 和小老鼠 JERRY 最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。

最近,TOM 老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中 ai​>aj​ 且 i<j的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。注意序列中可能有重复数字。

Update:数据已加强。

输入格式

第一行,一个数 n,表示序列中有 n个数。

第二行 n 个数,表示给定的序列。序列中每个数字不超过 10^9。

输出格式

输出序列中逆序对的数目。

输入输出样例

输入 #1复制

6
5 4 2 6 3 1

输出 #1复制

11

说明/提示

对于 25% 的数据,n≤2500

对于50% 的数据,n≤4×10^4。

对于所有数据,n≤5×10^5



分析

如果直接使用二重循环,显然数据太大,一定会超时!!!!

归并排序思想的应用:

拿样例来说,最后一步归并时元素如下

2 4 5 | 1 3 6

可以看出,逆序对的个数可以有如下规律:对于右边的每一个元素,在左边找到第一个比右边大的元素的下标,每个右边的元素可以产生

3-左边元素的下标(从1开始)+1 个逆序对。由此可以写出如下代码

const int MAX = 5e5+5;

int N;
int a[MAX];
ll res;

void merge(int l1,int r1,int l2,int r2) {
    int i=l1,j=l2;
    int tmp[MAX],count=0;
    while (i<=r1&&j<=r2) {
        if(a[i]<=a[j]) tmp[count++]=a[i++];
        else {
            tmp[count++]=a[j++];
            res += r1-i+1;        //只会在这里产生逆序对
        }
    }
    while (i<=r1) tmp[count++]=a[i++];
    while (j<=r2) tmp[count++]=a[j++];
    for (int k = 0; k < count; ++k) {
        a[l1+k]=tmp[k];
    }
}

void mergeSort(int l,int r){
    if(l>=r) return;
    int mid = (l+r)/2;
    mergeSort(l,mid);
    mergeSort(mid+1,r);
    merge(l,mid,mid+1,r);
}



 

加强版逆序对

题目链接:P1966火柴排队

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int MAX = 5e5+5;
const int INF = 0x7fffffff;
const int MOD = 1e8-3;

int N,K;
int a[MAX];
ll res;

struct Point {
    int preValue,index;
}pa[MAX],pb[MAX];

void merge(int l1,int r1,int l2,int r2) {
    int i=l1,j=l2;
    int tmp[MAX],count=0;
    while (i<=r1&&j<=r2) {
        if(a[i]<=a[j]) tmp[count++]=a[i++];
        else {
            tmp[count++]=a[j++];
            res = (res+r1-i+1)%MOD;
        }
    }
    while (i<=r1) tmp[count++]=a[i++];
    while (j<=r2) tmp[count++]=a[j++];
    for (int k = 0; k < count; ++k) {
        a[l1+k]=tmp[k];
    }
}

void mergeSort(int l,int r){
    if(l>=r) return;
    int mid = (l+r)/2;
    mergeSort(l,mid);
    mergeSort(mid+1,r);
    merge(l,mid,mid+1,r);
}

bool cmp(const Point& aa,const Point& bb) {
    return aa.preValue<bb.preValue;
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
#ifdef LOCAL
    freopen("../data", "r", stdin);
#endif
    cin >> N;
    for (int i = 0; i < N; ++i) {
        cin >> pa[i].preValue;
        pa[i].index=i;
    }
    for (int i = 0; i < N; ++i) {
        cin >> pb[i].preValue;
        pb[i].index=i;
    }
    sort(pa,pa+N,cmp);
    sort(pb,pb+N,cmp);
    mergeSort(0,N-1);
    for (int i = 0; i < N; ++i) {
        a[pa[i].index]=pb[i].index;
    }
    mergeSort(0,N-1);
    cout << res << endl;
}


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章