1. 程式人生 > >莫隊演算法小介紹——看似暴力的莫隊演算法

莫隊演算法小介紹——看似暴力的莫隊演算法

摘要:

莫隊演算法是一個對於區間、樹或其他結構離線(線上)維護的演算法,此演算法基於一些基本演算法,例如暴力維護,樹狀陣列,分塊,最小曼哈頓距離生成樹,對其進行揉合從而產生的一個簡單易懂且短小好寫的演算法。此演算法在很多情況下可以很輕鬆的切掉一些複雜而且難寫的資料結構問題。

關鍵詞:

       程式設計、演算法、演算法優化,暴力演算法,分塊演算法,最小曼哈頓距離生成樹。

背景:

       眾所周知,在OI競賽、軟體的設計中都會要求我們去處理各種各樣的棘手的問題,而這些問題之中,有一大類就是維護問題:比如說對於一個序列的維護,對於棵二叉或者多叉樹的維護……這些問題往往會需要我們去使用一個或多個高階的資料結構複合來完美解決,通常題目的程式碼十分冗長而且出錯可能性十分大,是廣大OIer、Acmer、Coder所害怕的題目。那麼有沒有一種方法可以既簡單又快捷的解決這類問題(這類問題中的一大部分)呢?莫隊演算法就誕生辣!

理論1:

        序列莫隊:我們現在有一個長為n的靜態的序列,對於序列,我們有m次查詢,我們要動態查詢l到r之間大於a小於b的數的個數以及種類。遇到了這個問題我們通常需要使用書套樹的資料結構,即一顆以自平衡二叉查詢樹為節點的線段樹(時間複雜度大約是O(mlognlogn)),而且由於空間限制,我們還必須動態建立線段樹的節點,這樣一來十分難寫,一些大約要個400-500行,除錯起來也很困難。這時候我們來考慮暴力演算法,如果暴力的處理題目中的問題那麼複雜度是多少呢?這個不難計算,對於每個詢問我們都要O(n)的時間處理,一共有m個詢問,那麼暴力處理的複雜度就是O(nm)的,明顯處理問題花費的時間我們是不能接受的。這是我們想到可以交換詢問和詢問之間的先後次序,這樣每次詢問在前一次詢問的基礎上轉移就可以節省一些時間了。

       但是如何重新排列詢問之間的順序是一個問題。我們需要進行一些理論分析。我們再上一個詢問的基礎上暴力地維護一個詢問(假設上一個詢問詢問區間為[l0,r0],這個詢問區間為[l,r]),那麼我們所謂的暴力維護就是先把現有答案的右邊界從r0移動到r,再把左邊界從l0移動到l,那麼我們的總花費是O(|l-l0|+|r-r0|)。仔細看一看,沒錯,這就是我們的曼哈頓距離的計算公式,有了這個思路,我們就可以從圖形的角度來思考了,對於一個詢問[l,r]我們可以將它對映為平面上在(l,r)位置的點,那麼兩個詢問之間轉移的代價就是詢問所對應的點之間的曼哈頓距離。有了這一個結論,我們便想到可以用最小曼哈頓生成樹來處理詢問的順序。由此莫隊演算法便誕生啦!莫隊演算法就是先將詢問抽象成平面上的點,然後進行一邊最小曼哈頓距離生成樹,然後按照生成樹的順序來處理詢問,這樣的演算法複雜度大約是O(mSqrt(n))的。如此,問題便簡單了許多。

       但是由於最小曼哈頓距離生成樹也不是那麼的好寫,所以莫隊演算法還能再簡單一點麼?我們思考是否可以用一個簡單而暴力的演算法代替莫隊演算法呢。很快便能想到分塊演算法。我們可以使用分塊演算法來處理詢問之間的次序問題。再去看那個詢問對應的點所在的平面,我們找到它的X軸,我們把X軸平均分割成r分,然後我們把在一個塊內的詢問統一先處理,不在一個塊內的詢問我們按照左端點升序右端點升序排序依次處理。這樣做有什麼好處呢?對於m幹個詢問,如果在一個塊裡面,那麼處理這些詢問花費的複雜度是O(n/r*n*m),如果有兩個詢問不在一個同一個塊裡面,按照我們之前的排序規則,我們把左區間和右區間在塊之間移動的次數最多為r*(n/r)*r次,那麼我們的複雜度就是O(r*(n/r)*r)次,經過簡單的數學分析,我們可以發現r=Sqrt(n)是時間複雜度最低為O(nSqrt(n))次,是可以接受的時間複雜度。這樣我們的莫隊演算法就又簡單有強大了。但是在另一些情況下,題目會無恥的限定我們可以使用的空間(一般不會,因為這樣高階資料結構的複合也難以解決這樣的問題了)。那麼如果空間被限定了,我們應該如何解決問題呢?其實很簡單, 還記得我們之前的r麼?我們為了求的時間複雜度最小令r=Sqrt(n),如果我們令r=n ^ (2 / 3),那麼便是一個時間複雜度和空間複雜度較為平衡的情況,這樣可以很好的解決問題。

例題:

       輸入資料首先輸入兩個整數N,Q,分別代表序列的長度和詢問的個數。這兩個數字將單獨佔據一行並用一個空格分開。輸入資料的第二行包含了N個由一個空格分開的正整數,代表了整個序列,從左向右依次編號為A1, A2……An。接下來Q行,每行兩個整數i,j表示了一個詢問區間。輸入資料保證1≤i <j<=N

例題:

2038: [2009國家集訓隊]小Z的襪子(hose)

Description

       作為一個生活散漫的人,小Z每天早上都要耗費很久從一堆五顏六色的襪子中找出一雙來穿。終於有一天,小Z再也無法忍受這惱人的找襪子過程,於是他決定聽天由命……       具體來說,小Z把這N只襪子從1N編號,然後從編號LR(L 儘管小Z並不在意兩隻襪子是不是完整的一雙,甚至不在意兩隻襪子是否一左一右,他卻很在意襪子的顏色,畢竟穿兩隻不同色的襪子會很尷尬。你的任務便是告訴小Z,他有多大的概率抽到兩隻顏色相同的襪子。當然,小Z希望這個概率儘量高,所以他可能會詢問多個(L,R)以方便自己選擇。

Input

       輸入檔案第一行包含兩個正整數N和M。N為襪子的數量,M為小Z所提的詢問的數量。接下來一行包含N個正整數Ci,其中Ci表示第i只襪子的顏色,相同的顏色用相同的數字表示。再接下來M行,每行兩個正整數L,R表示一個詢問。

Output

       包含M行,對於每個詢問在一行中輸出分數A/B表示從該詢問的區間[L,R]中隨機抽出兩隻襪子顏色相同的概率。若該概率為0則輸出0/1,否則輸出的A/B必須為最簡分數。(詳見樣例)

【樣例解釋】

詢問1:共C(5,2)=10種可能,其中抽出兩個2有1種可能,抽出兩個3有3種可能,概率為(1+3)/10=4/10=2/5。

詢問2:共C(3,2)=3種可能,無法抽到顏色相同的襪子,概率為0/3=0/1。

詢問3:共C(3,2)=3種可能,均為抽出兩個3,概率為3/3=1/1。

注:上述C(a, b)表示組合數,組合數C(a, b)等價於在a個不同的物品中選取b個的選取方案數。

【資料規模和約定】

30%的資料中 N,M ≤ 5000;

60%的資料中 N,M ≤ 25000;

100%的資料中 N,M ≤ 50000,1 ≤ L < R ≤ N,Ci ≤ N。

單個測試點時限2S

       對於上述這道題,30%的資料我們可以對於每個詢問都掃描詢問區間中所存在的數然後計算,這樣單次複雜度是O(N)的,但有M的詢問,總複雜度是O(MN)。這就顯得有點不太能接受了。

但是當我們知道一個詢問[l,r]的答案後,[l+1,r],[l-1,r],[l,r+1],[l,r-1]這四個區間的答案可以通過計算做到O(1)的時間內得到

所以我們可以考慮莫隊演算法,分為如下三步。

1、分塊

2、把所有詢問左端點排序

3、對於左端點在同一塊內的詢問按右端點排序,然後分三種情況統計。

而複雜度正如理論部分所說的一樣,

一、ii+1在同一塊內,r單調遞增,所以rO(N)的。由於有sqrt(N)塊,所以這一部分時間複雜度是Nsqrt(N)。
二、ii+1跨越一塊,r最多變化n,由於有sqrt(N)塊,所以這一部分時間複雜度是Nsqrt(N)
三、ii+1在同一塊內時變化不超過sqrt(N),跨越一塊也不會超過2* sqrt(N),不妨看作是sqrt(N)。由於有N個數,所以時間複雜度是O(Nsqrt(N))
可以證明覆雜度是O(Nsqrt(N))

理論2:

       可現在有很多問題都設定了修改操作,對於這類我們我們又該如何處理呢?

問題:

       我們現在有一個長為n的,對於序列,我們有m次操作,操作分為兩種

1、詢問在[l,r]中抽到兩個數字相同的概率

2、把某個位置的數ai改成x

100%的資料中 N,M ≤100000,1 ≤ L < R ≤ N,Ci ≤ N。

單個測試點時限10S

我們會發現,加上了修改操作後。就沒辦法直接按照分塊來處理解決詢問的順序。

定義B為分塊的大小。

       首先考慮沒有修改操作,那麼就和理論1中小Z的襪子一樣,令B = sqrt(n) 。把所有詢問左端點排序,對於左端點在同一塊內的詢問按右端點排序,然後寫莫隊演算法,按順序掃詢問,這樣是O(n sqrt(n))。如果現在加上修改操作考慮一個詢問(l,r),這樣是肯定不夠的。

       於是變成:(l,r,ti),ti是詢問時的時間,即這次詢問是第幾次操作。把所有詢問左端點l排序,對於左端點在同一塊內的詢問按右端點r所在的塊排序,對右端點r所在塊相同的我們再按照時間ti排序。

然後做莫隊演算法,按順序掃詢問,時間有時向前有時倒流。這樣令B = n ^ (2 / 3),因為在每一塊中時間最多從1到T改變一次,設詢問操作p1次,修改操作p2次,則在最差情況下的時間複雜度是O(p1 n^(2 / 3)+p2 *n^(1 / 3)* n^(1 / 3))=O(n^(5 / 3))【n與m等價】,這在時限下基本是可以得到答案的。

       那麼還有個遺留的問題,如何處理時間。我們只需要記錄修改前和修改後該點的值就可以了。

       至此這個問題完美解決。

總結:

       也許莫隊是一種看起來複雜度非常高的演算法,但如果合理地處理好分塊的大小和詢問的順序,,它便可以變成一個極其有效的工具。

辭謝

Vfleaking、莫濤

參考文獻

國家集訓隊命題《小z的襪子》

P.S如果想練習的話歡迎點選部落格分類——莫隊演算法