莫隊算法入門

Talk about 莫隊

莫隊算法,是莫濤dalao發明的一個神奇的優化暴力算法,它使用看似很simple的指針移動操做以及分塊的思想來將複雜度優化至\(O(n\sqrt n)\)html

莫隊的基本思想也很簡單:git

  1. 離線操做,在後面會提到咱們經過排序來下降複雜度
  2. 設以前咱們以及求出了區間\([l,r]\)的答案,那麼咱們考慮如何快速轉移到\([l+1,r],[l-1,r],[l,r-1],[l,r+1]\)
  3. 每一次利用以前的信息跳動指針便可得出答案

不過若是是這樣的話,只要出題人把數據造坑一點,讓你\(l,r\)指針一直左右移動,就能夠卡到\(O(n^2)\)我還不如寫暴力算法

因此莫隊的精髓來了,既然都是詢問,那咱們是否能夠經過適當地改變詢問的順序來讓\(l,r\)跳轉的幅度更小一點。數組

全部咱們能夠利用分塊的思想來優化:對於兩個詢問,若在其\(l\)在同塊,那麼將其\(r\)做爲排序關鍵字,若\(l\)不在同塊,就將\(l\)做爲關鍵字排序(這就是雙關鍵字)優化

這樣就能夠優化時間複雜度麼,咱們看一下嚴格的證實(摘自大米餅的博客):spa

首先,枚舉\(m\)個答案,就一個\(m\)了。設分塊大小爲\(unit\),元素\(i\)所屬的快爲\(blk_i\).net

分類討論:指針

\(l\)的移動:若下一個詢問與當前詢問的\(l\)所在的塊不一樣,那麼只須要通過最多\(2\cdot unit\)步可使得\(l\)成功到達目標.複雜度爲:\(O(m\cdot unit)\)code

\(r\)的移動:\(r\)只有在\(blk_l\)相同時纔會有序(其他時候仍是瘋狂地亂跳,你知道,一提到亂跳,那麼每一次最壞就要跳\(n\)次!),\(blk_l\)何時相同?在同一塊裏面\(blk_i\)相同。對於每個塊,排序執行了第二關鍵字: \(r\)。因此這裏面的\(r\)是單調遞增的,因此枚舉完一個塊,\(r\)最多移動n次。總共有\(\frac{n}{unit}\)個塊:複雜度爲:\(O(\frac{n^2}{unit})\)htm

總結:\(O(n\cdot unit+\frac{n^2}{unit})\)\(n,m\)同級,就統一使用\(n\)

根據基本不等式得:當\(unit=\sqrt n\)時,獲得莫隊算法的真正複雜度:\(O(n\sqrt n)\)

而後除此之外莫隊還有一個更加NB的常數優化,即在cmp時寫成:

return blk[a.l]<blk[b.l]||(blk[a.l]==blk[b.l]&&(blk[a.l]&1?a.r<b.r:a.r>b.r));

其實也很好理解吧,當左端點同塊時判斷一下當前快編號的奇偶性,儘可能讓右端點波動範圍較小(相似波浪形)


經典板子題——區間不一樣元素個數

板子題參考:SPOJ D-query

大體題意:給定一個數組,每次詢問一個區間內有多少不一樣的元素

很簡單的莫隊板子題,對於每一次加入新的數時都判斷一下這個數以前是否出現過便可,刪除時同理。

別忘記離散化,CODE

#include<cstdio>
#include<cctype>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=30005,M=200005;
struct data
{
    int l,r,ans,id;
}q[M];
int a[N],b[N],n,m,size,tot,blk[N],res,L,R,cnt[N];
inline char tc(void)
{
    static char fl[100000],*A=fl,*B=fl;
    return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(int &x)
{
    x=0; char ch; while (!isdigit(ch=tc()));
    while (x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc()));
}
inline void write(int x)
{
    if (x>9) write(x/10);
    putchar(x%10+'0');
}
inline bool cmp1(data a,data b)
{
    return blk[a.l]<blk[b.l]||(blk[a.l]==blk[b.l]&&(blk[a.l]&1?a.r<b.r:a.r>b.r));
}
inline bool cmp2(data a,data b)
{
    return a.id<b.id;
}
inline int find(int x)
{
    int l=1,r=tot,mid;
    while (l<=r)
    {
        mid=l+r>>1; if (b[mid]==x) return mid;
        if (b[mid]<x) l=mid+1; else r=mid-1;
    }
}
inline void add(int col)
{
    if (++cnt[col]==1) ++res;
}
inline void del(int col)
{
    if (--cnt[col]==0) --res;
}
int main()
{
    //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
    register int i; read(n); size=sqrt(n);
    for (i=1;i<=n;++i) read(a[i]),b[i]=a[i],blk[i]=(i-1)/size+1;
    sort(b+1,b+n+1); tot=unique(b+1,b+n+1)-b-1;
    for (i=1;i<=n;++i) a[i]=find(a[i]);
    for (read(m),i=1;i<=m;++i) read(q[i].l),read(q[i].r),q[i].id=i;
    sort(q+1,q+m+1,cmp1); L=q[1].l; R=q[1].r;
    for (i=L;i<=R;++i) add(a[i]); q[1].ans=res;
    for (i=2;i<=m;++i)
    {
        while (L>q[i].l) add(a[--L]); while (L<q[i].l) del(a[L++]);
        while (R<q[i].r) add(a[++R]); while (R>q[i].r) del(a[R--]);
        q[i].ans=res;
    }
    for (sort(q+1,q+m+1,cmp2),i=1;i<=m;++i) write(q[i].ans),putchar('\n');
    return 0;
}
相關文章
相關標籤/搜索