\(zl\) 姐姐有一串數,由於學生化太頭禿了,所以現在他想問你 \(m(m≤1e5)\) 次,其中 \(l\) 到 \(r\) 區間出現次數在\(3\)次及以上的數有多少個?
線段樹效率低下,不好維護。
(\(p.s.\) : \(lmpp\) 巨佬說如果 \(3\) 次可以取等的話,線段樹反而效率更高,巨佬們可以自己嘗試,菜比這裡就不演示了)
故引入莫隊——一種處理區間問題的離線演算法。
莫隊演算法,其中的「莫」指國家隊莫濤巨佬,ccccorz。
莫隊是優美的暴力。
先讓我們回到開頭來幫幫 \(zl\) 姐姐。
\(continue\) 是個傻子,所以他打了個暴力;
for(int i=l;i<=r;i++)
如果每次詢問都這麼打的話,很明顯, \(o(nm)\) 的演算法是會讓 \(zl\) 姐姐難堪的。(\(zl\) 姐姐:你來真的?)
聰明的你撿起了傻子 \(continue\) 打的暴力,覺得好不容易打的,扔了多可惜啊。
於是你開始對剛剛的暴力結果進行改造。
你想,既然我們已經知道了 \([l,r]\) 的結果,那麼 \([l-1,r]\),\([l+1,r]\),\([l,r-1]\),\([l,r+1]\)的結果不就可以也一起很容易得到了嗎?
在 \(o(1)\) 的時間裡,現在你的手裡現在已經有了 \(5\) 個答案。
這是多好的事,於是你將這個性質推廣到了所有的詢問。
詳細的,為了方便,我們不妨將推廣得來的四個答案一類稱作推廣區間,將推廣區間們對應的原區間 \([l,r]\) 稱作原區間。只要我們知道了原區間的答案,那麼要求的推廣區間便也就可求了。
所以現在問題就轉化為了:「如何使詢問區間成為乙個推廣區間」。進一步地,由於我們無法改變詢問,這個問題變成了「如何使推廣區間匹配上詢問區間」。
顯然,我們可以通過不斷修改原區間的方式,來匹配與詢問區間一致的推廣區間。
很明顯,這種不斷變化範圍的操作,我們可以通過 \(while\) 迴圈實現。
可是如果每次都 \(while\) ,我們的**仍然是乙份傻子**——會 \(t\) 得慘不忍睹(\(g2020\)
\(lvt\)
\(&&\)
\(dlz\)大佬語)
所以接下來才是真正應用時的莫隊:分塊+\(sort\)。
有了上面的一些推論,現在你意識到,每次詢問時都要根據查詢區間的大小調整原區間大小,且由於詢問區間並不相同(否則該問題將沒有意義),所以這個操作是必然的。
在必然的情況下,我們要盡可能的使該操作盡量的快,由此才能做到優美的暴力。
再次分析上面的過程,我們發現該操作的主要時耗**於鎖定所需區間的過程,所以我們應盡可能的將每次需要的推廣區間之間的差減小,以此來減少變化區間範圍的次數,提高了效率。
而達到此目的的唯一方式就是對查詢區間進行排序。
這便是優美莫隊裡面的\(sort\)部分。
至於排序的標準,自然要依靠於分塊啦~
由於我們要求兩個區間盡量的相似,所以應滿足單調性,不然會浪費時間。
如圖。
注:紫色曲線代表每次鎖定區間時需移動的長度。
圖一是未排序的效果,可以看見陰影的部分我們是重複移動了的,這樣十分浪費時間。
只要排序成圖二這樣,要移動的區間就再也不會重疊啦~
確定了排序的任務,那麼排序的關鍵字呢?
答案是分塊。
分塊合理地將節點劃分了不同的區間,這樣就可以較快的比較。
我們通過左端點所處的塊進行排序,若處於同乙個塊則比較右端點。這樣就可以科學有效的降低時間啦~
上面的都懂了,接下來就是乙份普通莫隊的模板**,一般的題都可以變著花樣套板子。(當然不能算帶權莫隊樹上莫隊)
\(problem\):hh的項鍊
這道題 \(luogu\) 是卡了莫隊的(但還是有神仙巨佬卡過去了),正解是樹狀陣列。故在這裡只是作為練手題。a6個點,t4個點就差不多了。主要是思想,思想!
#include #include #include #include using namespace std;
const int maxn=2e6+10;
const int inf=1<<30;
inline int read()
while(ch>='0'&&ch<='9')
return s*f;
}int n,m;
struct num
a[maxn];
int temp[maxn];
int bel[maxn]; //belong
bool cmp(num x,num y)
sort(a+1,a+m+1,cmp);
int l=1,r=0;
for(int i=1;i<=m;i++)
sort(a+1,a+m+1,cmp2); //離線演算法按原序輸出答案
for(int i=1;i<=m;i++)
printf("%d\n",a[i].num);
return 0;
}
在 \(zl\) 姐姐感激的眼神鼓舞下,更進一步吧!
\(zl\) 姐姐有一串數,由於他太可愛了,所以現在原基礎上增加乙個操作:將第 \(k\) 個數變成 \(num\)。
由於現在的問題仍然保留詢問操作,所以我們仍考慮使用莫隊解決。
由於修改數值的操作,我們需要莫隊可以儲存資料。由此我們引入乙個新的結構:帶權莫隊。
帶權莫隊的基本原理和普通莫隊是一樣的。
只會打暴力的傻子 \(continue\) 是這麼想的:
只要每次一輸入和當前區間有關的修改,就馬上暴力修改
。
很明顯,這會 \(tle\) ,因為區間可能不定。
這很像最開始我們處理基礎莫隊時遇到的問題。那麼這次同樣的,我們使用 \(sort\) 來解決。
我們新增乙個關鍵字 \(tim\)
\((time)\) ,其中記錄了對當前詢問有影響的修改操作的序號。\(sort\) 的時候將 \(tim\) 作為第三關鍵字,這樣既能保證基礎莫隊對詢問操作處理的正確性,又能及時處理修改操作。因為修改操作複雜度低,所以這樣就不會 \(tle\) 了。
修改操作單獨建乙個結構體, \(perfect\)。
\(problem\):數顏色
這道題題解裡面有乙個巨佬,在修改操作的時候很神仙的運用了轉換的思想,我的**寫得差多了,所以傳送門就放在這裡,大家可以去看。
這題還是卡莫隊,所以分塊的塊數設定為常數就不會卡了。
很多大佬都是將分塊的塊數 \(k\) 設定為的
(啊沒錯這個圖是複製的),因為這樣最快。其具體證明戳這位大佬的題解,菜比我證不來也看不懂(
感謝 \(zl\) 小姐姐不知不覺間提供給我的精神支援。
感謝我自己是個大菜比。
莫隊學習筆記
text text 借用的內容 一般的區間問題都可以使用莫隊。這是離線莫隊的裸的板子 真的就這麼短的四句話只是add和del裡面要加內容。至於塊的大小在這兒 對於複雜度的分析 text sum x i sqrt n sqrt o n sqrt text 由此可以推出 莫隊的複雜度大概就是乙個 the...
學習筆記 莫隊
題意簡述 給定乙個長度為 n 的序列,每次給定乙個區間 l,r 求 r l 1 count 1 times count 2 times times count n 先預處理出階乘和階乘的乘法逆元,然後每次更新除掉原來的數乘上後來的數就好了 code below include include inc...
學習筆記 樹上莫隊
其實樹上莫隊是乙個尤拉序而已嘛,像普通的莫隊,特判一下出現過兩次的值就行了 設 st i 為 i 進棧的時間,ed i 為 i 出棧的時間,dfn x,那麼就可以分兩種情況 1 y 在 x 子樹中,也就是 lca x,y x 那麼區間轉化成 st x,st y 2 y 不在 x 子樹中,也就是 lc...