zl 姐姐有一串數,因爲學生化太頭禿了,因此如今他想問你 m(m≤1e5) 次,其中L到R區間出現次數在3次及以上的數有多少個?ios
效率低下,很差維護。算法
故引入莫隊——一種處理區間問題的離線算法。數組
莫隊算法,其中的「莫」指國家隊莫濤巨佬,CCCCOrz。ide
莫隊是優美的暴力。spa
先讓咱們回到開頭來幫幫 zl 姐姐。blog
Continue 是個傻子,因此他打了個暴力;排序
for(int i=l;i<=r;i++) { cnt[a[i]]++; if(cnt[a[i]]>=3) ans++; }
若是每次詢問都這麼打的話,很明顯, O(nm) 的算法是會讓 zl 姐姐難堪的。(zl 姐姐:你來真的?)get
聰明的你撿起了傻子 Continue 打的暴力,以爲好不容易打的,扔了多惋惜啊。it
因而你開始對剛剛的暴力結果進行改造。io
你想,既然咱們已經知道了 [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 <iostream> #include <cstdio> #include <algorithm> #include <cmath> using namespace std; const int maxn=2e6+10; const int inf=1<<30; inline int Read() { int s=0; int f=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { s=(s<<1)+(s<<3)+ch-'0'; ch=getchar(); } return s*f; } int n,m; struct Num { int l,r; //詢問的區間 int num; //詢問的答案 int id; //詢問的次序 }a[maxn]; int temp[maxn]; int bel[maxn]; //belong bool cmp(Num x,Num y) { return ((bel[x.l]==bel[y.l]) && (x.r<y.r)) || (bel[x.l]<bel[y.l]); } bool cmp2(Num x,Num y) { return x.id<y.id; } int cnt[maxn]; //記錄次數的數組 int top; int ans; //答案有幾個 inline void Add(int x) { cnt[x]++; if(cnt[x]==1) ans++; } inline void Dele(int x) { cnt[x]--; if(!cnt[x]) ans--; } int main() { n=Read(); int k=sqrt(n); //分塊 for(int i=1;i<=n;i++) { temp[i]=Read(); bel[i]=(i-1)/k+1; } m=Read(); for(int i=1;i<=m;i++) { int x,y; x=Read(); y=Read(); if(x>y) swap(x,y); a[i].l=x; a[i].r=y; a[i].id=i; } sort(a+1,a+m+1,cmp); int l=1,r=0; for(int i=1;i<=m;i++) { int x=a[i].l; int y=a[i].r; //鎖定區間的過程 while(l>x) Add(temp[l-1]),--l; while(l<x) Dele(temp[l]),++l; while(r<y) Add(temp[r+1]),++r; while(r>y) Dele(temp[r]),--r; a[i].num=ans; } sort(a+1,a+m+1,cmp2); //離線算法按原序輸出答案 for(int i=1;i<=m;i++) printf("%d\n",a[i].num); return 0; }
可愛的 zl 小姐姐感謝了你。終於,大家都有了光明的將來……!