莫隊演算法學習筆記

2021-07-28 15:10:57 字數 2074 閱讀 8722

莫隊演算法

有時候我們經常會碰到這樣一類問題:給定n和n個數etc,然後給出m組區間詢問[l,r],要求對所有詢問區間給出答案。

然後發現這類題通常有乙個很好的性質就是,如果你知道了[l,r]的答案,就可以o(1)或者o(lgn)(再大就有點玄了)的知道

使得根據第i個區間[li,ri]的答案拓展到第i+1個區間[l_(i+1),r_(i+1)]的答案的用時之和最少。

好吧,上面這句話有點難懂。我們再詳細點說。

不妨假設你可以做到o(1)的擴充套件左右端點,即由[l,r]的答案可以o(1)的知道[l+1,r],[l-1,r],[l,r+1],[l,r-1]的答案。

那麼,如果你知道了[li,ri]的答案,你就可以在o( | li - l_(i+1) | + | ri - r_(i+1) | )的複雜度上知道[l_(i+1),r_(i+1)]的答案。

如果把[l,r]看作平面上的點(l,r),那麼從乙個點(也就是答案)擴充套件到另乙個點(下乙個答案)就是兩點的曼哈頓距離。

我們可以證明存在一種走法,走過所有點,且移動距離之和是o(n*sqrt(n))的。

但是這個大概常數巨大而且不好寫,所以我們有一種替代品,也可以說是改良版,就是分塊。

具體做法是這樣的:

我們把所有詢問都進來,然後把那個長為n的序列分塊,分為sqrt(n)塊。

按照左端點所在塊的編號(記為pos( query[i].left ))公升序排序,如果左端點在同乙個塊內,就按照右端點公升序排序。

有讀者可能會問,為什麼不直接按照左端點公升序-右端點公升序排序呢?

事實上,讀者可以構造乙個例子,在最壞情況下,按照這種暴力排序的方法複雜度是o(n^2)級別的。

那麼問題來了:為什麼莫隊演算法的排序保證時間複雜度是o(n*sqrt(n))的呢?

簡單證明如下:

當我們第i個詢問轉移的第i+1個詢問時

1)如果第i個詢問區間和第i+1個詢問區間的左端點所在塊的編號相同,那麼左端點的移動不會超過sqrt(n)。

也就是說,左端點一直在塊內移動的總複雜度為o(n*sqrt(n))(因為左端點最多轉移n次,減去左端點跨越塊的部分,不足n)

同時由於右端點公升序,那麼若s,s+1,,,t-1,t的詢問區間左端點所在塊的編號相等,那麼右端點的移動不會超過n次。有一位有sqrt(n)個塊,

所以這一部分的複雜度是o(n*sqrt(n))的。

2)考慮左端點跨越塊的情況,每次跨越最大是o(2*sqrt(n))那麼左端點跨越塊的複雜度o(n*sqrt(n))的。

又在這個期間,每次左端點跨越的時候,右端點可能要移動o(n)次,一共左端點跨越sqrt(n)個塊,所以右端點複雜度是o(n*sqrt(n))的。

綜上莫隊演算法的排序保證時間複雜度是o(n*sqrt(n))的。

可以進一步的說,莫隊演算法的總複雜度是o(m*sqrt(n)*f(n)+m*g(n))的,m是詢問數。其中f(n)是拓展一次端點的複雜度,通常為o(1)或者o(lgn)。g(n)是根據現有資訊計算乙個區間答案的複雜度,通常為o(1)或者o(lgn)。由此可以看出,當我們有多種可以維護莫隊演算法的資料結構的時候,要盡量使修改操作複雜度低。乙個例子是bzoj3809的一道題,如果用樹狀陣列,修改和查詢都是o(lgn)的,那麼套乙個莫隊複雜度就是o(m*sqrt(n)*lgn)的。但是我們可以用分塊代替,這樣雖然詢問是o(sqrt(n))的,但是修改卻變成o(1)的了,總複雜度就是o(m*sqrt(n))。

莫隊演算法模板:

#include#include#include#define maxn //最大序列長度 

#define maxm //最大操作次數

#define pos(i) (i/sz)

using namespace std;

int ans[maxm],ans,sz;

//以及其他輔助陣列和資料結構

struct queryq[maxm];

bool cmp(const query &q1,const query &q2)//排序比較的函式

for(int i=1;i<=m;i++)

printf("%d\n",ans[i]);//輸出答案

return 0;

}

大概就是這樣。

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

前言 在學習莫隊演算法之前,我一直以為這是乙個很高深的演算法。實際上,它就是乙個很高深的演算法 這個演算法玄學地將分塊與暴力兩大演算法實現了二合一,從而打造出了乙個時間複雜度為o n n o n sqrt n o nn 的求解多個區間詢問的離線演算法。具體介紹 首先,我們以詢問中l ll所在的區間為...

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

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

莫隊演算法學習筆記(二) 帶修莫隊

莫隊演算法,是乙個十分優雅的暴力。普通的莫隊可以輕鬆解決一些離線問題,但是,當遇上了一些有修改操作的問題,普通莫隊就無能為力了。於是,改進後的莫隊 帶修莫隊就這樣產生了。接下來,我們一起在普通莫隊的基礎之上,學會帶修莫隊這個強大無比的演算法。既然是帶修莫隊,那麼第乙個關鍵問題就是如何處理修改。其實,...