1. 程式人生 > >2017級演算法模擬上機準備篇(歸併排序)

2017級演算法模擬上機準備篇(歸併排序)

歸併排序是分治法的一個最經典也是最基礎的應用 

Divide And Conquer的思想很重要

歸併排序的的Divide採用了簡單的二分 Conquer採用的是將兩個有序數組合併為一個有序陣列。

2014-Inverse number:Reborn 逆序數求解

#include <algorithm>
#include <iostream>
#include <cstring>
using namespace std;
const int maxlen=1e6 + 10;
long long ans;
int ar[maxlen];
void
Merge(int begin,int end){ int temp[maxlen]; int i,j,k,p,q; int middle=(begin+end)/2; p=begin; q=middle+1; k=begin; while(p<=middle && q<=end){ if(ar[p]<=ar[q]) temp[k++]=ar[p++]; else { temp[k++]=ar[q++]; ans+=middle-p+1
; } } while(p<=middle) temp[k++]=ar[p++]; while(q<=end) temp[k++]=ar[q++]; for(k=begin;k<=end;k++) ar[k]=temp[k]; } void MergeSort(int begin,int end){ int i,j; if(begin < end){ int middle=(begin + end)/2; MergeSort(begin,middle); MergeSort(middle
+1,end); Merge(begin,end); } } int main(){ int n,i,j,k; while(~scanf("%d",&n)){ for(i=0;i<n;i++) scanf("%d",&ar[i]); ans=0; MergeSort(0,n-1); printf("%lld\n",ans); } return 0; }

利用歸併排序來求解逆序數最巧妙的地方是在原本的合併兩個有序陣列的過程中,借用了有序的思想。

如果將一個數組分為兩部分,那麼逆序數只可能在左右兩部分或者橫跨兩部分中出現,顯然在子陣列都有序的情況下。

逆序數只會出現在橫跨兩部分之間,那麼其實在合併兩個子陣列的過程中,就可以順便統計

核心程式碼:

if(ar[p]<=ar[q]) temp[k++]=ar[p++];
        else {
            temp[k++]=ar[q++];
            ans+=middle-p+1;
        }

2015-模式尋對 幾乎是歸併排序的翻版 

但值得注意的是在歸併排序的過程中,其實也在修改陣列的內容,記得要復原,或者使用其他方法來儲存。

2016-D&C--玲瓏數 (逆序數的進階版)

這道題在歸併排序的基礎上做了很大的改變

關鍵點是限制了條件後,無法使用排序過程的trick,只能在歸併排序的基礎上,利用有序性移動指標來減少部分時間損耗。

#include <algorithm>
#include <iostream>
#include <cstring>
using namespace std;
const int maxlen=1e6 + 10;
long long ans;
long long ar[maxlen];
long long br[maxlen];
void Merge(long long begin,long long end){
    long long temp[maxlen];
    long long i,j,k,p,q;
    long long middle=(begin+end)/2;
    p=begin;
    q=middle+1;
    k=begin;
    while(p<=middle && q<=end){
        if(ar[p]<=ar[q]) temp[k++]=ar[p++];
        else temp[k++]=ar[q++];
    }
    while(p<=middle) temp[k++]=ar[p++];
    while(q<=end) temp[k++]=ar[q++];
    for(k=begin;k<=end;k++)
        ar[k]=temp[k];
}
void MergeSort(long long begin,long long end){
    int i,j;
    if(begin < end){
        int middle=(begin + end)/2;
        MergeSort(begin,middle);
        MergeSort(middle+1,end);
        j=middle+1;
        for(i=begin;i<=middle;i++){
            while(j<=end && ar[i]>2*ar[j])
                j++;
            ans+=j-(middle+1);
        }
        Merge(begin,end);
    }
}
int main(){
    long long n,i,j,k,t,x,y;
    while(~scanf("%lld",&n)){
         for(i=0;i<n;i++)
            scanf("%lld",&br[i]);
            scanf("%lld",&t);
        while(t--){
            ans=0;
            scanf("%lld %lld",&x,&y);
            if(x>y) swap(x,y);
            for(i=x;i<=y;i++)
                ar[i]=br[i];
          MergeSort(x,y);
        printf("%lld\n",ans);
        }
    }
    return 0;
}

關鍵程式碼是利用有序性的雙指標移動遍歷法。0(n)

j=middle+1;

for(i=begin;i<=middle;i++){

while(j<=end && ar[i]>2*ar[j])

j++;

ans+=j-(middle+1);

}

還有兩個小的坑點吧,一是給定的區間是p和q,但是前後次序卻沒有給定,必要時要通過swap函式來調整。

還有就是一個老生常談的問題2*int 可能會爆int 解決辦法就是能long long 絕不int

2017-序列優美差值 (再度進階版)

給定一個序列a,詢問滿足i<ji<j且 La[j]a[i]RL≤a[j]−a[i]≤R的點對i,j(i,j)數量

#include <algorithm>
#include <iostream>
#include <cstring>
using namespace std;
const int maxlen=1e6 + 10;
long long ans;
long long ar[maxlen];
long long br[maxlen];
long long L,R;
void Merge(long long begin,long long end){
    long long temp[maxlen];
    long long i,j,k,p,q;
    long long middle=(begin+end)/2;
    p=begin;
    q=middle+1;
    k=begin;
    while(p<=middle && q<=end){
        if(ar[p]<=ar[q]) temp[k++]=ar[p++];
        else temp[k++]=ar[q++];
    }
    while(p<=middle) temp[k++]=ar[p++];
    while(q<=end) temp[k++]=ar[q++];
    for(k=begin;k<=end;k++)
        ar[k]=temp[k];
}
void MergeSort(long long begin,long long end){
    int i,j;
    if(begin < end){
        int middle=(begin + end)/2;
        MergeSort(begin,middle);
        MergeSort(middle+1,end);
        
        j=middle+1;
        int st,ed;
        
        st=ed=begin;
        for(i=middle+1;i<=end;i++){
            while(ar[i]-ar[ed] >= L && ed<=middle)
                ed++;
            while(ar[i]-ar[st] > R && st<=middle)
                st++;
            ans+=ed-st;
        }
        Merge(begin,end);
    }
}
int main(){
    long long n,i,j,k,t,x,y,T;
    scanf("%lld\n",&T);
    while(T--){
        scanf("%lld %lld %lld",&n,&L,&R);
         for(i=0;i<n;i++)
            scanf("%lld",&ar[i]);
            ans=0;
          MergeSort(0,n-1);
          printf("%lld\n",ans);
    }
    return 0;
}

這個問題的求解十分的巧妙 利用歸併排序來求解問題 就要想辦法利用有序性,本題是雙指標移動,第一步求解其實已經利用一個數組的單調性,st和ed的單向移動利用了另一個數組的單調性,非常巧妙。

2017—陣列優美和值(字首和 + 分治排序)