(翻譯 部分總結)莫隊演算法入門小結

2021-07-22 04:19:31 字數 4064 閱讀 4948

最近入門了莫隊演算法,覺得好玄妙,為自己寫個小結。

我初學莫隊演算法是通過這個部落格理解的:戳這裡,只不過是英文的,其他人也寫過這篇博文的翻譯,特此註明。

下面我的小結也主要圍繞這個問題來談。

考慮這個問題:

給定乙個序列,共有m次詢問,每次詢問區間中出現次數大於等於3的數字有多少個。

我們先來考慮n,m較小的情況。

暴力統計可以解決,用cn

t[n]

表示n在該子串行中的數量,直接統計答案。

int solve(int l,int r)

return rtn;

}

這個樣子做,複雜度看得出來,最壞情況下(每次查詢整個區間)是o(

nm) 的。

我們得想辦法優化一下。

…………

…………

…………

由於我們並沒有涉及到修改操作,所以每乙個a[

i]對答案的貢獻一定是固定的,因而我們在多次查詢中,會重複統計許多a[

i]對答案的貢獻。事實上,上乙個查詢的區間如果與下乙個查詢的區間有交集(甚至重合)的話,我們是完全沒有必要再統計一遍他們公共部分的答案的!

看下面這段**:

int n,m;

int cnt[100010],a[100010];

int ans[100010],now_ans;//now_ans:當前答案 ans[i]:第[i]次詢問的答案

struct queryq[100010];//記錄每個查詢的資訊

void del(int

pos)

void inc(int

pos)

void solve()

while(now_r<=rr)

while(now_lwhile(now_r>rr+1)

ans[i]=now_ans;}}

噫!一下子變得這麼長了!還套進了四個while迴圈和兩個函式!

不要急不要急,如果你能理解這段**,那就是學會了半個莫隊演算法呢!

首先看到solve函式:

這段**的核心思想是只修改上乙個區間與當前區間的不同位置,因此我們需要從記錄的上乙個區間的兩個頂點出發(now_l和now_r),移動到當前查詢的區間兩端點,沿途修改答案

那麼那麼為什麼是這樣移動的呢?

那麼那麼為什麼還要+1-1什麼的呢?

別急……

對於左端點而言,它到右端點的這一區間是已經統計過了的。那麼如果它還要往右移動,就必然會經過已統計過答案的區間!那麼這就使原來統計過的部分留在了左端點左邊(也就是說,不在我的下乙個統計區間了),因此,左端點要向右移動的話,沿途的部分對答案的貢獻需要被刪除。與此同時,如果向左移動,左端點左邊的區間必然不在我的原區間內,所以向左移動,需要增加它對答案的貢獻。

右端點也是可以模擬的。

至於加一減一,大於等於的問題,我們可以畫出上個區間與下個區間關係的不同情況,自己按照演算法推演一下,也就不難理解了。(其實是我不會qaq)

…… ……

…… 請仔細理解上面的**,然後再往下看。

我們還是不難發現,這個**的複雜度,取決於now_l和now_r的移動次數,最壞情況下,每次查詢,都要從頭已到尾,就依然是個o(

nm) 的演算法,本質上沒什麼提高。

說好的優化呢???

別急……

既然複雜度取決於兩端點的移動,那麼……

我們合理安排一下查詢的順序,讓兩端點科學有效地運動,不就好了嗎?

怎麼排序呢……

now_l:按我排序,按左端點從小到大排序!我最多隻從左到右走一遍!

now_r: →_→那我呢……我每次不是就只能瞎跑了……你倒好只跑一遍,我每查一次都恨不得要跑一趟tot

now_l:(⊙o⊙)…那怎麼辦……

now_r:你看啊,按你排序,我每次的複雜度就是o(

n),按我排序,你的複雜度就是o(

n),所以啊……我們可以委曲求全……搞個o(

n√) 出來。

now_l :蛤?我們妥協是怎麼妥協出o(

n√) 來的?

now_r :→_→你沒學過分塊嗎……

now_l :我只是個端點而已……

now_r :……

我們將1-n分成o(

n√) 個塊,每個塊內大致就有o(

n√) 個元素,每個元素也就有自己對應的塊編號。對於所有的詢問,我們找到左端點,按照它所在塊的編號排序。

now_l:那有好多查詢的塊編號一樣的呢!(now_r:閉嘴!)

對於塊編號相等的詢問,我們就按照右端點的公升序排列。

例如對於如下詢問:

我們先按照塊編號排序:

再對同一塊內的查詢按右端點公升序排序:

按照這個順序處理所有詢問。

現在,我們就面臨莫隊演算法的最後乙個問題:

複雜度?

我們來這麼看:

對於右端點而言,在每個塊中,由於按照公升序排列,所以最多移動n次,由於總共有n√個塊,所以右端點最多移動nn

√ 次。(或者說是n3

2 )

對於左端點而言,每次詢問的移動雖然不確定,但是由於我把同乙個塊的元素排在了一起,所以每次查詢,都相當於在塊內移動,因而最大幅度為mn

√ 次。

綜上,莫隊演算法的複雜度為o(

(n+m

)n√)

即o(nn√)

(或o(

n32)

)。到此,莫隊演算法的基本概念介紹完畢。

這是解決這個問題的最終**:

#include 

#include

#include

#include

using namespace std;

int n,m,num;

int cnt[100010],a[100010];

int ans[100010],now_ans;//now_ans:當前答案 ans[i]:第[i]次詢問的答案

struct queryq[100010];//記錄每個查詢的資訊

int cmp(query x,query y)

void inc(int

pos)

void solve()

while(now_r<=rr)

while(now_lwhile(now_r>rr+1)

ans[q[i].id]=now_ans;//q[i]實際上是第q[i].id次詢問

}}int main()

sort(q+1,q+1+m,cmp);//排序

solve();

for(int i=1;i<=m;i++)printf("%d\n",ans[i]) ;

return

0;}

神奇的莫隊演算法,只通過排序就可以將o(

nm) 的演算法重置為o(

nn√)

o(1) 內完成,有可能是o(

logn

) 甚至是o(

n√) ,無形中加大了複雜度。

3.在10

5 的範圍內,莫隊演算法的表現很不錯,甚至可以與一些o(

logn

) 的演算法相近,但是資料規模上到106

的話,莫隊演算法的表現就夠嗆了。比如cf 703d這道題,相關的操作非常適合莫隊,但是由於資料範圍達到106

,儘管時限達到3.5s,但是不加快速讀入輸出以及其他優化的話,很容易得tle。

下面就是我用cena測試的結果:

90以內的資料都在105

以內,91-100規模為200000,101-110為500000,111-120為1000000.

2016.08.27 未完待續

莫隊入門總結

這是一篇適合蒟蒻的講解 大佬可以自行離開 莫隊是一種是離線的演算法 即它在詢問的時候是不會修改的,所以我們可以通過調整詢問的次序來獲得答案。比如區間3到5和區間3到6 他們之間只差了1 於是我們只需要看下新加進來的這個元素對原來答案的影響就好了 對吧?問題是 我們應該如何給詢問區間排序使得時間複雜度...

莫隊演算法入門

昨天重溫了一下captainmo的職業生涯 莫隊的模板,看了下別人的部落格,把三個板子打了,做練習前先小小總結了一下吧。一.基礎莫隊演算法 莫隊演算法 離線 暴力 分塊,它通常用於不修改只查詢的一類區間問題,複雜度為 主要就是通過排序過後再處理詢問能優化暴力,排序則是利用分塊,至於為什麼更優,附張別...

總結 莫隊演算法

機房中的各位神仙都會莫隊就我不會,然後如果有些題實在想不出也可以用這個做一下。如果一些操作可以在知道 ans l,r 的情況下,o 1 的時間內求出 ans l 1,r ans l 1,r ans l,r 1 ans l,r 1 那麼就可以用莫隊求解。將操作離線 按照分塊的思路排序 這是保證複雜度合...