求逆序對經常使用的兩種算法 ----歸併排 & 樹狀數組


網上看了一些歸併排求逆序對的文章,又看了一些樹狀數組的,以爲本身也寫一篇試試看吧,而後本文大致也就講個思路(沒有例題),可是仍是會有個程序框架的
好了下面是正文算法


  1. 歸併排求逆序對
  2. 樹狀數組求逆序對

1、歸併排求逆序對

 

舒適提示:閱讀這段內容須要的知識點:歸併排序數組

— 首先的話,歸併排序你們應該都知道的吧?歸併排是利用分治的思想,先分後和,分到左右區間相等或相交時在返回上一層進行兩個有序小數組交錯插入排序,造成一個有序數組,而後層層返回排好序的數組,做爲新的小數組插入大數組排序,這就是一個n log n的排序算法(帶 log 的算法通常都算是比較快的,只要常數不過大)。而後仍是不懂的同窗能夠百度,這裏不細講了。另外提一提,實在是不會用歸併排的話冒泡也是同樣能夠求逆序對的,累加的話就變成了判斷到須要交換時進行,但冒泡的複雜度高了點,是 n^2 了,提交 後會爆幾個點就不知道了,得看具體題目和數據。(反正數據通常不會水到讓你滿分【斜眼笑ing】)框架

— 其次的話,用歸併排求逆序對無非也就是在插入的過程當中將 逆序數 ans 累加,而後也沒什麼不一樣的了,只要記得歸併排模板的話基本也是碼的出來的。(我的感受歸併排求逆序隊仍是挺清晰的,由於這樣基本就是套套模板不用想太多)ui

模板以下,但請別直接複製粘貼,好歹本身打一遍spa

int n,ans;
const int mod=99999997;
int f[100005],g[100005];

void merge_sort(int l,int r)
{
    if(l>=r)  //若是說l、r交錯的話直接return無論
        return ;

    int mid=(l+r)>>1;   //以l、r的中點爲界向下分支排序
    merge_sort(l,mid);
    merge_sort(mid+1,r);

    int i=l,j=mid+1,k=l;
    while(i<=mid && j<=r)      //保證兩個小的數組不超邊界
    {
        if(f[i]<f[j])
            g[k++]=f[i++];
        else
        {                //大概要在模板上作修改的就是這塊了,用ans把逆序對累加
            ans=(ans+mid-i+1)%mod;    //若是題目中有取餘就%mod
            g[k++]=f[j++];
        }
    }
    // 而後把剩下的數直接插入到大的數組末尾(但不會對ans進行累加操做)
    while(i<=mid)
        g[k++]=f[i++];
    while(j<=r)
        g[k++]=f[j++];
    for(int i=l;i<=r;++i)    //g數組只是一箇中間量,用完就丟了,f纔是要排序的數組
        f[i]=g[i];

}

二次分析

–而後我以爲還得解釋一下爲何ans在j數組(即第二個小數組)中的值插入到達數組的時候才累加。試想,逆序對就是大的數字在前面,小的數字在後面,每次發現一組這樣的數字對那麼整個數組中的逆序對數量就能夠+1了。
如:1 2 6 8 和 3 5 7 9 ,初始i指向1,j指向3,k指向8,l指向9,ans=0
在第一次比較時,1<3,則1插入進大數組,i++,ans不變
第二次比較式,i指向了2, 2<3,則2插入進大數組,仍是i++,ans不變
第三次,i指向6,6>3,3入大數組,j++,ans+=2 。
這裏就是重點了,3小於6,則3也必定小於6後面的數,而且能夠和這些數(共兩個)分別對應造成n個逆序對(n爲k-i+1,即6和6的後面總共還剩多少個數)
第四次也同樣,是j++,ans+=2,此時ans爲4,原理同上,再也不解釋
而後就是繼續向大數組隊尾插入數了,咱們發現直到 i 數組爲空時(全被插入完畢了),j 數組仍有剩餘,那麼就將 j 數組直接插入進大數組,但ans不進行累加(由於此時 i 數組空了,沒法與 j 數組中剩下的數造成逆序對)調試

呼~這樣總該解釋的差很少了,同志們自個兒好好消化消化吧。code

2、樹狀數組求逆序對

blog

舒適提示:閱讀本段須要具有的知識點:樹狀數組的基本操做(update、getsum、lowbit之類的)排序

–首先的話,樹狀數組我也不來講這麼詳細了,許多細節方面(如 getsum 時x爲何要減去一個lowbit(x)了之類的)的理解就麻煩請本身思考得出或是去問百度了。圖片

–其次的話,樹狀數組其實就是代碼短一點(短一點就好碼一點,好碼一點就好調試一點,好調試一點就不容易出錯一點),看着舒服吧。
而後我就不囉嗦了,直接上代碼吧。

int lowbit(int x)       //lowbit求最末尾的1所在的位置
{
    return x&(-x);
}

void update(int x,int k)    //update等會兒講
{
    for(;x<=n;x+=lowbit(x))
        g[x]+=k;
}

long long getsum(int x)     //getsum的話。。。也等會兒講
{
    long long res=0;
    for(;x;x-=lowbit(x))    //一直跳向比x小的數,如7->6->4->0(結束)
                            //或是6->4->0(結束)
        res+=g[x];
    return res;
}

void BIT()      //這個BIT啊,我看書的時候也不知道是什麼鬼,
//而後才發現原來是樹狀數組英文名(Binary Indexed Trees)的縮寫
{
    for(int i=1;i<=n;++i)
    {
        update(f[i],1);
        ans=(ans+i-getsum(f[i]))%mod;
    }
}

另外提一點,不要看着這個代碼好像行數不少,碼一遍以後會發現真的很短

而後講講update和getsum吧(主要是給學過的人講,談談個人理解)

上圖!

這裏寫圖片描述
在這裏的話,你能夠認爲每一個三角形的頂端都是一個BOSS,一旦他們的下屬出現了以後,下屬會先+1,再逐級向上彙報(也就是說有小三角形的話就先向小三角形上的BOSS先彙報,而後再由這個小的BOSS向更大的BOSS彙報,直到頂層), 這樣的話咱們最後就能夠清晰地獲得一個實時更新的樹狀數組,每一個g中所存的就是它以及它的下屬目前已出現的個數

這裏寫圖片描述

這裏的話 6 在getsum的時候路徑爲6->4->0(結束),獲得的 res 爲 1,即已出現的數字中,小於等於6的數字只有一個

這裏寫圖片描述

那麼上面演示的是有逆序對的狀況,一樣的,你也能夠自行演示一下 先 2 後 6的狀況,這時候你會發現 ans 並無累加,即沒有逆序對的狀況……
總之,仍是要熟知樹狀數組操做裏的含義吧。

好了,心血來潮寫的一篇博客終於搞定了。(大概花了兩個多小時的樣子,是否是蒟蒻?) 而後若是說有哪裏我講的不對的話,歡迎各位 神(da)犇(lao)在評論區裏噴我。_ (:зゝ∠) _ bye bye(下次見)! 1000010 1011001 1000101————!(一串ASCII碼)

相關文章
相關標籤/搜索