莫隊演算法學習筆記(一) 普通莫隊

2021-08-21 20:09:20 字數 3881 閱讀 9303

前言

在學習莫隊演算法之前,我一直以為這是乙個很高深的演算法。(實際上,它就是乙個很高深的演算法)

這個演算法玄學地分塊暴力兩大演算法實現了二合一,從而打造出了乙個時間複雜度為o(n

n)

o(n\sqrt n)

o(nn​)

求解多個區間詢問離線演算法

具體介紹

首先,我們以詢問中l

ll所在的區間為第一關鍵字,以r

rr的位置為第二關鍵字進行排序。

然後,我們可以先暴力求解第乙個詢問,並將l

ll指標指向第乙個詢問的l

ll,將r

rr指標指向第乙個詢問的rrr。

隨後,對於第i

ii個問題,我們都可以將當前詢問的區間[li

,ri]

[l_i,r_i]

[li​,r

i​]的答案從上一次詢問的區間[li

−1,r

i−1]

[l_,r_]

[li−1​

,ri−

1​]的答案推得。只要將l

ll指標從li−

1l_

li−1

​這個位置開始向l

il_i

li​一點點移動,並且一邊移動一邊更新答案(更新答案的過程一般是先減去原先的答案,然後再加上更新後的新答案),對r

rr指標的操作類似。

一道模板題

這樣的描述畢竟不夠具體,下面讓我們看一道模板題:【洛谷2709】小b的詢問

l in

klink

link

【洛谷2709】小b的詢問 的題解詳見部落格【洛谷2709】小b的詢問(莫隊模板題)。

就拿我部落格中的**為例,我們來具體地看一下幾個地方。

第乙個地方:如何將讀入的詢問排序。

我們可以在讀入序列的資訊的同時對該序列進行分塊

//第34行

pos[i]

=(i-1)

/sqrt

(n)+1;

//將序列分塊

然後,在讀入每乙個詢問時,我們不僅要將每個詢問區間的邊界l

ll和r

rr儲存下來,我們同時也要儲存這個詢問是第幾個讀入的:

//第35行

for(i=

1;i<=q;

++i)

read

(q[i]

.l),

read

(q[i]

.r),q[i]

.pos=i;

//儲存下來每乙個詢問

這樣一波操作之後,我們就可以對詢問進行排序了:

//第26~29行

bool

cmp(query x,query y)

//排序所需的cmp()函式

//第36行

sort

(q+1

,q+q+

1,cmp)

;//將詢問排序

這應該是莫隊演算法中比較核心的部分了。這裡以l

ll指標為例,r

rr指標的操作也是類似的,具體可以參考我的**。

第一種情況,若l

i].l

lli].l

//第43行

ans-

=cnt[a[l]

]*cnt[a[l]],

--cnt[a[l]

],ans+

=cnt[a[l]

]*cnt[a[l]],

++l;

//我們可以用cnt陣列來儲存每個數字出現的次數,用ans來儲存答案。先將ans減去原先的答案(即cnt[a[l]]的平方),然後更新cnt[a[l]](將cnt[a[l]]減1),再將ans加上新的答案(更新後的cnt[a[l]]的平方),最後將l指標加1即可(這個操作類似於乙個刪除操作,所以是先刪除,再更新指標)

第二種情況,若l

>q[

i].l

l>q[i].l

l>q[

i].l

//第44行

--l,ans[q[i]

.pos]

-=cnt[a[l]

]*cnt[a[l]],

++cnt[a[l]

],ans[q[i]

.pos]

+=cnt[a[l]

]*cnt[a[l]];

//先將l指標減1,然後將ans減去原先的答案,在更新cnt[a[l]](將cnt[a[l]]加1),最後將ans加上新的答案(這個操作類似於乙個增加操作,所以是先更新指標,再增加,與上面剛好相反)

複雜度分析

分析完了**,最後,我們來分析一波時間複雜度

對於l

ll指標,有兩種情況:①在塊內移動。由於每個塊的大小為n

\sqrt n

n​,所以移動的時間複雜度為o(n

)o(\sqrt n)

o(n​

),由於總共有q

qq個詢問,所以總複雜度為o(q

n)

o(q\sqrt n)

o(qn​)

。②移動到了下乙個塊。由於每個塊的大小為n

\sqrt n

n​,所以每次移動到下乙個塊的時間複雜度為o(n

)o(\sqrt n)

o(n​

),又因為總共有n

\sqrt n

n​個塊,所以這種情況下的總複雜度為o(n

)o(n)

o(n)

。綜上所述,l

ll指標的移動複雜度大致為o(q

n)

o(q\sqrt n)

o(qn​)

。對於r

rr指標,我們可以發現,只要l

ll指標所在的塊不變,r

rr指標指向的位置始終是**單調遞增(或遞減)**的,也就是說,對於同乙個塊內的l

ll指標,r

rr指標的移動複雜度是o(n

)o(n)

o(n)

的。又因為總共有n

\sqrt n

n​個塊,所以r

rr指標總的移動複雜度是o(n

n)

o(n\sqrt n)

o(nn​)

的。因此總共的複雜度為o((

n+q)

n)

o((n+q)\sqrt n)

o((n+q

)n​)

,由於一般來說n=q

n=qn=

q,所以可以近似地把時間複雜度當做o(n

n)

o(n\sqrt n)

o(nn​)

,這應該是比較快的了。

莫隊演算法,就是這樣乙個玄學的演算法,相當於乙個巧妙的暴力。

例題附註,另一道例題:【洛谷1494】[國家集訓隊] 小z的襪子

l in

klink

link

【洛谷1494】[國家集訓隊] 小z的襪子 的題解詳見部落格【洛谷1494】[國家集訓隊] 小z的襪子(莫隊)

莫隊演算法學習筆記(三) 樹上莫隊

樹上莫隊的核心思想,就是將一棵樹轉化成乙個序列,然後用普通莫隊來搞。以一棵樹為例 要想對這棵樹進行樹上莫隊,我們第一步就是用乙個 s 陣列把它的括號序存下來 id 12 3456 78910 1112 1314 1516 s 12 4788 7455 2366 31 同時,我們用 i 陣列儲存每個數...

莫隊講解 普通莫隊

結束了分塊,我們來講下莫隊。據我所知,莫隊能解決一切區間問題,除了翻轉。因為它就是個暴力 其實這兩者的關係並不大。僅僅是時間複雜度一樣而已。我們把原序列分成 n塊 好像就是這裡相同 這裡說的序列是查詢序列l r,並不是讀入的a i 之後我們把序列排序 按照第一關鍵字為左端點所在的塊的大小,如果相同就...

莫隊演算法學習筆記

莫隊演算法 有時候我們經常會碰到這樣一類問題 給定n和n個數etc,然後給出m組區間詢問 l,r 要求對所有詢問區間給出答案。然後發現這類題通常有乙個很好的性質就是,如果你知道了 l,r 的答案,就可以o 1 或者o lgn 再大就有點玄了 的知道 使得根據第i個區間 li,ri 的答案拓展到第i ...