歸併排序思想的應用:求逆序對

前言

學了這麼久的歸併排序,感覺沒什麼卵用。。。。而且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;
}


 

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