莫隊演算法

weixin_34377065發表於2016-03-15

莫隊演算法詳解

  本文翻譯自MO’s Algorithm (Query square root decomposition),作者anudeep2011,發表日期為2014-12-28。由於最近碰到一些莫隊演算法的題目,找到的相關中文資料都比較簡略,而這篇英語文章則講解的比較詳細,故翻譯成中文與大家分享。由於本人水平有限,錯誤在所難免,請諒解。下面是譯文。

  我又發現了一個有用,有趣但網上資源非常少的話題。在寫作之前,我做了一個小調查,令我驚訝的是,幾乎所有的印度程式設計師都不知道該演算法。學習這個很重要,事實上所有的codeforces紅名程式設計師都使用這個演算法,比如在div 1 C題和D題中。在一年半以前沒有這方面的題目,但從那時起這類題目的數量就爆發了!我們可以期待這在未來的比賽中會有更多的這類題目。

一、問題描述

  給定一個大小為N的陣列,陣列中所有元素的大小<=N。你需要回答M個查詢。每個查詢的形式是L,R。你需要回答在範圍[ L,R ]中至少重複3次的數字的個數。

例如:陣列為{ 1,2,3,1,1,2,1,2,3,1 }(索引從0開始)

查詢:L = 0,R = 4。 答案= 1。在範圍[L,R]中的值 = { 1,2,3,1,1 },只有1是至少重複3次的。

查詢:L = 1, R = 8。答案= 2。在範圍[L,R]中的值 = { 2,3,1,1,2,1,2,3 }, 1重複3遍並且2重複3次。至少重複3次的元素數目=答案= 2。

 

複雜度O(N^2)的簡單的解法

對於每一個查詢,從L至R迴圈,統計元素出現頻率,報告答案。考慮M = N的情況,以下程式在最

壞的情況執行在O(n^2)

for each query:

  answer = 0

  count[] = 0

  for i in {l..r}:

    count[array[i]]++

    if count[array[i]] == 3:

      answer++

對上述演算法稍作修改。它仍然執行在O(n^2)

add(position):

  count[array[position]]++

  if count[array[position]] == 3:

    answer++

 

remove(position):

  count[array[position]]--

  if count[array[position]] == 2:

    answer--

 

currentL = 0

currentR = 0

answer = 0

count[] = 0

for each query:

// currentL 應當到 L, currentR 應當到 R

  while currentL < L:

    remove(currentL)

    currentL++

  while currentL > L:

    add(currentL)

    currentL--

  while currentR < R:

    add(currentR)

    currentR++

  while currentR > R:

    remove(currentR)

    currentR--

output answer

最初我們總是從L至R迴圈,但現在我們從上一次查詢的位置調整到當前的查詢的位置。

如果上一次的查詢是L = 3,R = 10,則我們在查詢結束時有currentL=3、currentR=10。如果下一個查詢是L = 5,R = 7,則我們將currentL 移動到5,currentR 移動到7。

add 函式 意味著我們新增該位置的元素到當前集合內,並且更新相應的回答。

remove 函式 意味著我們從當前集合內移除該位置的元素,並且更新相應的回答。

 

二、一個解決上述問題的演算法及其正確性

  莫隊演算法僅僅調整我們處理查詢的順序。我們得到了M個查詢,我們將把查詢以一個特定的順序進行重新排序,然後處理它們。顯然,這是一個離線演算法。每個查詢都有L和R,我們稱呼其為“起點”和“終點”。讓我們將給定的輸入陣列分為√N塊。每一塊的大小為 N/√N=√N。每個“起點”落入其中的一塊。每個“終點”也落入其中的一塊。

  如果某查詢的“起點”落在第p塊中,則該查詢屬於第p塊。該演算法將處理第1塊中的查詢,然後處理第2塊中的查詢,等等,最後直到第√N塊。我們已經有一個順序、查詢按照所在的塊升序排列。可以有很多的查詢屬於同一塊。

  從現在開始,我會忽略(其它,譯者注,所有括號內斜體同)所有的塊,只關注我們如何詢問和回答第1塊。我們將對所有塊做同樣的事。(第1塊中的)所有查詢的“起點”屬於第1塊,但“終點”可以在包括第1塊在內的任何塊中。現在讓我們按照R值升序的順序重新排列這些查詢。我們也在所有的塊中做這個操作。(指每個塊塊內按R升序排列。)

最終的排序是怎樣的? 下面例子,看懂了就明白這個演算法了

所有的詢問首先按照所在塊的編號升序排列(所在塊的編號是指詢問的“起點”屬於的塊)。如果編號相同,則按R值升序排列。

例如考慮如下的詢問,假設我們會有3個大小為3的塊(0-2,3-5,6-8):

{0, 3} {1, 7} {2, 8} {7, 8} {4, 8} {4, 4} {1, 2}

讓我們先根據所在塊的編號重新排列它們

{0, 3} {1, 7} {2, 8} {1, 2} (|){4, 8} {4, 4}(|) {7, 8}

現在我們按照R的值重新排列

{1, 2} {0, 3} {1, 7} {2, 8}(|) {4, 4} {4, 8}(|) {7, 8}

現在我們使用與上一節所述相同的程式碼來解決這個問題。上述演算法是正確,因為我們沒有做任何改變,只是重新排列了查詢的順序。

三、對上述演算法的複雜性證明 - O(N^1.5)

我們完成了莫隊演算法,它只是一個重新排序。可怕的是它的執行時分析。原來,如果我們按照我上面指定的順序,我們所寫的O(N2)的程式碼執行在O(√N×N)時間複雜度上。可怕,這是正確的,僅僅是重新排序查詢使我們把複雜度從O(N2)降低到O(√N×N),而且也沒有任何進一步的程式碼上的修改。好哇!我們將以O(√N×N)的複雜度AC。

看看我們上面的程式碼,所有查詢的複雜性是由 4個while迴圈決定的。前2個while迴圈可以表述為“左指標(currentL)的移動總量”,後2個 while迴圈可以表述為“右指標(currentR)的移動總量”。這兩者的和將是總複雜性。

最重要的。讓我們先談論右指標。對於每個塊,查詢是遞增的順序排序,所以右指標(currentR)按照遞增的順序移動。在下一個塊的開始時,指標可能在extreme end (最右端?) ,將移動到下一個塊中的最小的R處。這意味著對於一個給定的塊,右指標移動的量是O(N)。我們有O(√N)塊,所以總共是O(N*√N)。太好了!

讓我們看看左指標怎樣移動。對於每個塊,所有查詢的左指標落在同一個塊中,當我們從一個查詢移動到另個一查詢左指標會移動,但由於前一個L與當前的L在同一塊中,此移動是O(√N)(塊大小)的。在每一塊中左指標的移動總量是O(Q*√N),Q是落在那個塊的查詢的數量。對於所有的塊,總的複雜度為O(M*√N)。

就是這樣,總複雜度為O((N+M)*√N)=O(N*√N)

 

四、上述演算法的適用範圍

如前所述,該演算法是離線的,這意味著當我們被強制按照特定的順序查詢時,我們不能再使用它。這也意味著當有更新操作時我們不能用這個演算法。不僅如此,一個重要的可能的侷限性:我們應該能夠編寫add 和remove函式。會有很多的情況下,add 是平凡的 (指複雜度O(1)?),但remove不是。這樣的一個例子就是我們想要求區間內最大值。當我們新增的元素,我們可以跟蹤最大值。但當我們刪除元素則不是平凡的。不管怎樣,在這種情況下,我們可以使用一個集合來新增元素,刪除元素和報告 最小值(作者想說最大值?)。在這種情況下,新增和刪除操作都是O(logN)(導致了O(N*√N*logN)的演算法)。

在許多情況下,我們可以使用此演算法。在一些情況下,我們也可以使用其它的資料結構,如線段樹,但對於一些問題使用莫隊演算法的是必須的。讓我們在下一節中討論幾個問題。

習題和示例程式碼

DQUERY – SPOJ:區間內不同元素的數量=出現次數>=1的元素個數,所以跟以上討論的問題是一樣的。

點選此處檢視示例程式碼

注意:該程式碼提交會超時,加上更快的輸入輸出(傳說中的輸入輸出掛?) 就能AC。為了使程式碼整潔,去掉了快的輸入輸出。(評論中說將remove 和add 宣告為inline行內函數就可以AC。)

Powerful array – CF Div1 D: 這道題是必須用莫隊演算法的例子。我找不到任何其它解法。 CF Div1 D 意味著這是一道難題。看看用了莫隊演算法就多簡單了。 你只需要修改上述程式碼中的add(), remove() 函式。

GERALD07 – Codechef

GERALD3 – Codechef

Tree and Queries – CF Div1 D

Powerful Array – CF Div1 D

Jeff and Removing Periods – CF Div1 D

這篇文章講的好詳細,瞬間就喜歡上了莫隊演算法。排個序暴力下就能簡潔的做出來的演算法誰不喜歡。
 

相關文章