1. 程式人生 > >歸併排序之神奇的逆序對

歸併排序之神奇的逆序對

文章包含四部分

  1. 歸併排序的實現(略講)

  2. 歸併排序求逆序對

  3. 歸併排序例題詳講

  4. 樹狀陣列和歸併排序求逆序對的區別

  • 關於歸併排序的實現

歸併排序主要分為兩大步:

1.分解

2.合併

關於歸併排序的具體原理請自行百度. 這裡只略講一下歸併排序的程式碼實現方式

  • 分解

我們採用二分遞迴的方式處理

  • 合併

我們採用三個指標來實現

板子題:快速排序

Code:

#include<iostream>
#include<cstdio>

using namespace std;

const int N=100000+5;

int n;
int a[
N];//需要排序的陣列 int r[N];//用來輔助排序的陣列 void msort(int s,int t) { if(s==t) return;//如果只有一個數就返回 int mid=(s+t)/2; //遞迴分解 msort(s,mid);//分解左邊 msort(mid+1,t);//分解右邊 int i=s,j=mid+1,k=s; //合併左右序列 while(i<=mid && j<=t) //正常排序合併 { if(a[i]<=a[j]) {r[k]=a[i];k++
;i++;} else {r[k]=a[j];k++;j++;} } while(i<=mid){r[k]=a[i];k++;i++;}//複製排序後左邊子序列剩餘 while(j<=t){r[k]=a[j];k++;j++;}//複製排序後右邊子序列剩餘 for(int i=s;i<=t;i++) a[i]=r[i];//更新a陣列 } 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; }

測試結果:

第一個是STLSTL裡的sortsort 第二個是開了O2O2sortsort 第三個是手寫的歸併排序

noipnoip不能開O2O2

QwQQwQ

  • 關於歸併排序求逆序對

模板題

關於逆序的定義:大的數排在小的數前面

當然你也可以用氣泡排序

實現方法:

每次合併的時候,記錄有多少次交換

假定我們要將一串無序數字排成升序(從小到大) 根據歸併排序的原理,每次我們進行合併操作的時候,左邊子序列中小的數都會被合併進輔助陣列。 這時如果左邊子序列有數剩餘,則說明這些數都比右邊子序列的數要大,那麼我們可以根據這一點來計算逆序對的個數

核心Code:

while(i<=mid && j<=t)
    {
        if(a[i]<=a[j]) {r[k]=a[i];k++;i++;}
        else {r[k]=a[j];k++;j++;ans+=mid-i+1;}//核心操作
		//mid-i+1即左邊子序列剩餘元素的個數 
    }

其餘部分沒有什麼差別

模板題Code:

#include<iostream>
#include<cstdio>

using namespace std;

const int N=5e5+5;

long long n,ans;
int a[N];
int r[N];

void msort(int s,int t)
{
    if(s==t) return;
    int mid=(s+t)/2;
    msort(s,mid);
    msort(mid+1,t);
    int i=s,j=mid+1,k=s;
    while(i<=mid && j<=t)
    {
        if(a[i]<=a[j]) {r[k]=a[i];k++;i++;}
        else {r[k]=a[j];k++;j++;ans+=mid-i+1;}//核心操作
		//mid-i+1即左邊子序列剩餘元素的個數 
    }
    while(i<=mid){r[k]=a[i];k++;i++;}
        
    while(j<=t){r[k]=a[j];k++;j++;}
        
    for(int i=s;i<=t;i++)
        a[i]=r[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;
}

  • 歸併排序例題詳講

關鍵詞:

交換序列中相鄰的兩個元素

用最少的交換次數使原序列變成不下降序列

顯然在求逆序對的個數QwQQwQ

套上板子即可

瑞士輪

這裡只用到了歸併排序中的合併操作

分析:

每組比賽的勝者:賽前,總分是按降序排的;獲勝後都得1分,仍是降序;

每組比賽的負者:賽前,總分是按降序排的;不得分,仍是降序。

先按初始分數排序,然後按分數高低兩人一組比賽;

勝者入隊AA,負者入隊BB。這樣AABB自身仍是有序的;

只需進行合併操作即可,合併操作的複雜度是O(n)O(n),而如果用快排其複雜度為O(nlogn)O(nlogn)

Code:

#include<iostream>
#include<cstdio>
#include<algorithm>

using namespace std;

const int N=1e5+5;

int n,r,q;
struct fengzi
{
    int id,sorce,power;
}m[2*N],x[2*N],y[2*N];
//m是選手,x是勝利者,y是失敗者 
bool cmp(fengzi a,fengzi b)
{
    if(a.sorce>b.sorce) return 1;//依題意從高到低排序 
    if(a.sorce==b.sorce && a.id<b.id) return 1;//處理特殊情況 
    return 0;
}

//合併操作
void merge()
{
    int i=1,j=1,cnt=0;
    while(cnt<=2*n)
    {
        if(i>n)
        {
            while(j<=n){m[++cnt]=y[j];j++;}
            break;
        }
        if(j>n)
        {
            while(i<=n){m[++cnt]=y[i];i++;} 
            break;
        }
        if(x[i].sorce>y[j].sorce || (x[i].sorce==y[j].sorce && x[i].id<y[j].id))//依題意更新排名 
            m[++cnt]=x[i++];
        else m[++cnt]=y[j++];
    }
 } 

int main()
{
    scanf("%d%d%d",&n,&r,&q);
    for(int i=1;i<=2*n;i++)
    {
        scanf("%d",&m[i].sorce);//得分 
        m[i].id=i;//編號 
    }
    for(int i=1;i<=2*n;i++)
        scanf("%d",&m[i].power);//實力值 
    sort(m+1,m+1+2*n,cmp);//排序 
    for(int qwq=1;qwq<=r;qwq++)//列舉每場比賽 
    {
        int i=0,j=0;
        for(int ppp=1;ppp<=n;ppp++)//列舉每組選手 
        {
            int pos=2*ppp;
            if(m[pos-1].power>m[pos].power)//如果該組中的第一個選手比第二個選手強 
            {
                m[pos-1].sorce++;//第一個選手的分數+1 
                x[++i]=m[pos-1];//第一個選手分到勝利者的隊伍 
                y[++j]=m[pos];//第二個選手分到失敗者的隊伍 
            }
            else
            {
                m[pos].sorce++;
                x[++i]=m[pos];
                y[++j]=m[pos-1];
            }
        }
        merge();//合併勝利者和失敗者,更新  新的排名 
    }
    printf("%d",m[q].id);
    return 0;
}

火柴排隊

分析:

由數學知識可知,要使$ \sum (a_i-b_i)^2使最小 我們就要使每一個a_i-b_i$最小

也就是說,aa中第kk小的數要和bb中第kk小的數排在一個位置

依據這個思想,我們可以新建一個數組xx 這個陣列的下標是aa陣列中第kk小的數在原序列中的編號 這個陣列中存的是bb陣列中第kk小的數在原序列中的編號

例如:

aa的原始序列為5,3,1,2,65,3,1,2,6 bb的原始序列為7,2,9,3,17,2,9,3,1 把兩個序列從小到大排序後, aa序列為1,2,3,5,61,2,3,5,6 bb序列為1,2,3,7,91,2,3,7,9 我們假定此時的kk為3 aa序列中第3(k)3(k)小的數的原始編號就是22 bb序列中第3(k)3(k)小的數的原始編號就是44

x[3]=4x[3]=4

顯然,這個陣列的特性就是,如果x[i]=ix[i]=i那麼此時,aa序列中第k小的數就和bb序列中的第kk小的數,一一對應了,即原始序列時,就有aibia_i-b_i最小。

然後,我們的目標是,將xx陣列升序排列,求出交換的次數,這不就轉換成求逆序對了…

Code:

#include<iostream>
#include<cstdio>
#include<algorithm>

using namespace std;

const int mod=99999997;
const int N=100000+5;

int n,ans,cnt;

struct team
{
    int id,hight;//id是原始編號,hight是高度 
}a[N],b[N];
int x[N],y[N];
//x待排序的陣列
//y是輔助陣列 

bool cmp(team a,team b)
{
    return a.hight<b.hight;//先按高度排序 
}

void merge(int l,int r)
{
    if(l