淺談優雅的區間暴力莫隊算法
莫隊算法是優雅的暴力而不是什麽神奇的數據結構。
只要是區間離線可以算的莫隊幾乎都可以達到O(n sqrt (n) )的時間復雜度
為什麽叫莫隊算法呢?據說這是2010年國家集訓隊的莫濤在作業裏提到了這個方法。
由於莫濤經常打比賽做隊長,大家都叫他莫隊,該算法也被稱為莫隊算法。
一個例題:給出數組n個元素a[]和Q組詢問[L,R]求[L,R]中那些數字出現了T次?
Subtask1:對於99%的數據 1<=L<R<=1000 ,1<=n<=1000,1<=Q<=100
Subtask1.5:對於99.999%的數據 1<=L<R<=100000 ,1<=n<=100000,1<=Q<=10000且數據保證隨機
Subtask2:對於100%的數據 1<=L<R<=100000 ,1<=n<=100000,1<=Q<=10000且數據保證一定程度上的隨機
Subtask1怎麽做?
暴力?怎麽暴力:
- 我們用一個cnt[ ]數組記錄每種元素,用桶排的思想,枚舉區間,
- 每遇到一個元素對應的桶++,然後暴力一遍所有的桶,等於1的我們ans就++,
- 這樣統計不同的個數,看看是不是等於L到R,
- 然後再清空桶和ans,做下一組詢問。
好吧這樣你就學會了99%的莫隊算法。
Subtask1.5 優化暴力
顯然處理詢問的次序和時間復雜度有有關系,如果確定合理的次序這樣也不難成為一個好方法。。
剩下1%就是怎麽確定搞詢問的次序,一種可行的方法是讓L和R恰好單增,讓前面可以用的東西盡可能多
但是這樣的表現不好。特別是面對精心設計的數據,這樣方法(按照L排序R排序)表現得很差。
/* 舉個栗子,有6個詢問如下:(1, 100), (2, 2), (3, 99), (4, 4), (5, 102), (6, 7)。 這個數據已經按照左端點排序了。用上述方法處理時,左端點會移動6次,右端點會移動移動98+97+95+98+95=483次。 其實我們稍微改變一下詢問處理的順序就能做得更好:(2, 2), (4, 4), (6, 7), (5, 102), (3, 99), (1, 100)。 左端點移動次數為2+2+1+2+2=9次,比原來稍多。右端點移動次數為2+3+95+3+1=104,右端點的移動次數大大降低了。*/
首先:考慮我們有兩個指針。一個叫做curL,另一個叫curR。他們對應的是所指的數的標號。這樣我們可以利用這兩個指針進行移動,每次只能向左或向右移動一步。移動的復雜度是O(1)。
我們現在處理了curL—curR區間內的數據,現在左右移動,比如curL到curL-1,只需要更新上一個新的3,即curL-1。
那麽curL到curL+1,我們只需要去除掉當前curL的值。因為curL+1是已經維護好了的。
curR同理,但是要註意方向哦!curR到curR+1是更新,curR到cur-1是去除。
我們先計算一個區間 [curL curR] 的answer,這樣的話,我們就可以用O(1)轉移到 [curL-1 curR] [curL+1 curR] [curL curR+1] [curL curR-1] 上來並且求出這些區間的answer。
我們利用curL和curR,就可以移動到我們所需要求的[L R]上啦~
Subtask2: 怎麽優化1.5?——分塊
我們把所有的元素分成多個塊(即分塊)。分了塊跑的會更快。再按照右端點從小到大,左端點塊編號相同按右端點從小到大。
程序實現:
# include <bits/stdc++.h> using namespace std; const int MAXN=100005; struct rec{ int p,bl,l,r; }q[MAXN]; int n,m,bo[MAXN],a[MAXN],answer; int ans[MAXN]; bool cmp(rec a,rec b) { return (a.bl<b.bl||(a.bl==b.bl&&a.r<b.r)); } void add(int pos){ //將a[pos]加入並更新answer } void del(int pos){ //將a[pos]去除並更新answer } int main() { scanf("%d%d",&n,&m); int block=sqrt(n); for (int i=1;i<=n;i++) scanf("%d",&a[i]); for (int i=1;i<=m;i++) { scanf("%d%d",&q[i].l,&q[i].r); q[i].p=i; q[i].bl=(q[i].l-1)/block+1; } sort(q+1,q+1+m,cmp); int curL=0,curR=0; for (int i=1;i<=m;i++) { int L=q[i].l,R=q[i].r; while (curL>L) add(--curL); while (curL<L) del(curL++); while (curR>R) del(curR--); while (curR<R) add(++curR); ans[q[i].p]=answer; } for (int i=1;i<=m;i++) printf("%d\n",ans[i]); return 0; }
淺談優雅的區間暴力莫隊算法