1. 程式人生 > >淺談優雅的區間暴力莫隊算法

淺談優雅的區間暴力莫隊算法

turn its 這樣的 printf name 去除 分塊 ans 指針

莫隊算法是優雅的暴力而不是什麽神奇的數據結構。

只要是區間離線可以算的莫隊幾乎都可以達到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;
}

淺談優雅的區間暴力莫隊算法