前言
在學習莫隊演算法之前,我一直以為這是乙個很高深的演算法。(實際上,它就是乙個很高深的演算法)
這個演算法玄學地將分塊與暴力兩大演算法實現了二合一,從而打造出了乙個時間複雜度為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 ...