財神娛樂首存即享優惠回饋唷~詳情請進👉

『數據布局』莫隊、帶修莫隊、樹上莫Slot 教學隊詳解

平凡莫隊

簡介

莫隊是一種基于分塊思惟的離線算法,用于辦理區間成績,實用規模以下:

  1. 只有扣問沒有點竄。
  2. 許可離線。
  3. 在已經知扣問\([l,r]\)謎底的環境下可以\(O(1)\)失去\([l,r?1],[l,r+1],[l?1,r],[l+1,r]\)的謎底。

知足以上三個前提就可以在\(O(n\sqrt{m}+mlogm)\)的時間龐大度下失去每個扣問的解。

算法思惟

莫隊的精華就在于經由過程對扣問進行排序,并把扣問的效果作為下一個扣問求解的根基,使得暴力圖解的龐大度失去保障。

上文中“實用規模”的第三點“在已經知扣問$ [l,r]$謎底的環境下可以 \(O(1)\)失去\([l,r]\)的謎底”等于“把扣問的效果作為下一個扣問求解的根基”的要領。

例:[國度集訓隊]小Z的襪子

在這題中,用\(cnt_{i}\)透露表現當前處置的區間內顏色為\(i\)的襪子浮現的次數,用\(\mathrm{len}\)透露表現當前處置的區間的長度,用\(x\)透露表現新增的那只襪子的顏色。

以已經知區間\([l,r]\)的謎底求解區間\([l,r+1]\)為例。分手處置分子以及分母:

  • 分母為任選兩只襪子的組合總數,本來是\(\frac{\mathrm{len}(\mathrm{len}-1)}{2}\),目前是\(\frac{\mathrm{len}(\mathrm{len}+1)}{2}\),增長了\(\mathrm{len}\)

  • 分子為兩只襪子顏色雷同的組合總數,比原來增長了\(cnt_{x}\),即新增的這只襪子以及底本就在當前區間內的雷同顏色的襪子的組合。

是以,將一只顏色為\(x\)的襪子計入謎底的函數就可以寫進去了:

//fz透露表現分子,fm透露表現分母
inline void add(int x) {
    fz += cnt[x];
    cnt[x]++;
    fm += len;
    len++;
}

同理可以寫出將一只顏色為\(x\)的襪子移出謎底的函數:

inline void del(int x) {
    cnt[x]--;
    fz -= cnt[x];
    len++;
    fm -= len;
}

因而,咱們就可以失去一個暴力的算法:用\(l\)以及\(r\)分手記載當前區間的兩個端點,然后用上面這段代碼來更新謎底(\(q[i].l,q[i].r\)代表正在處置的扣問的兩個端點,\(col[p]\)代表第\(p\)只襪子的顏色):

while (l > q[i].l) add(col[--l]);
while (r < q[i].r) add(col[++r]);
while (l < q[i].l) del(col[l++]);
while (r > q[i].r) del(col[r--]);

然而,這個算法的時間龐大度是\(O(nm)\)的(由于最壞環境下每次\(l\)以及\(r\)兩個指針都要走\(O(n)\)的間隔,而一共有\(m\)次扣問),以及暴力齊全同樣甚至跑的更慢。

別忘了,之前我說過,莫隊的精華就在于經由過程對扣問進行排序,使得暴力圖解的龐大度失去保障。

咱們的目的是使\(l\)以及\(r\)兩個指針走過的總間隔盡可能的小,這時候候就要用到分塊的思惟了。

把整個區間\([1,n]\)分紅多少塊,以扣問的左端點地點塊為第一樞紐字,以扣問的右端點巨細為第二樞紐字,對扣問進行排序,那末:

  • 關于統一塊的扣問,\(l\)指針每次至多挪移塊的巨細,\(r\)指針的挪移則是單調的,統共挪移至多\(n\)。
  • 關于不同塊的扣問,\(l\)每次換塊時至多挪移兩倍塊的巨細,\(r\)每次換塊時至多挪移\(n\)。

總結:(用\(B\)透露表現塊的巨細)\(l\)指針每次挪移\(O(B)\),\(r\)指針每塊挪移\(O(n)\)。

以是:

  • \(l\)的挪移次數至多為扣問數\(\times\)塊的巨細,即\(O(mB)\)。

  • \(r\)的挪移次數至多為塊的個數×總區間巨細,即\(O(n^2\div B)\)。

是以,總挪移次數為\(O(mB+n^2/B)\)。

沒錯,這便是個雙勾函數,以是當\(B=\sqrt{\frac{n^2}{m}}\)即\(\frac{n}{\sqrt{m}}\)時龐大度最小,為\(O(n\sqrt{m})\)。

剩下的最初一個成績:初始確當前區間是甚么?

只需恣意指定一個空區間就好了,如\(l=1,r=0\)。

以是,整個莫隊算法就可以歸納綜合為:

  1. 將扣問記載上去。
  2. 以\(\frac{n}{\sqrt{m}}\)
  3. 為塊的巨細,以扣問的左端點地點塊為第一樞紐字,以扣問的右端點巨細為第二樞紐字,對扣問進行排序。
  4. 暴力處置每個扣問。
  5. 輸入謎底。

總的龐大度為\(O(n\sqrt{m}+mlogm)\)。

P.S. 網上許多教程說分塊巨細取\(\sqrt{n}\)最優,龐大度為\(O(n\sqrt{n})\),這是不謹嚴的,當\(n,m\)差別較大時使用\(\sqrt{n}\)作為分塊巨細效率會明明偏低。

例題代碼

[國度集訓隊]小Z的襪子AC代碼:

#include<iostream>
#include<algorithm>
#include<cstdio>
#includ大樂透100組開獎號碼e<cstring>
#include<線上真人麻將推薦cmath>

using namespace std;

const int MAXN=50005;
int n,m,B,fz,fm,len,col[MAXN],cnt[MAXN],ans[MAXN][2];
struct query {
    int l,r,id;
    bool operator < (query & b) {
        return l / B == b.l / B ? r < b.r : l < b.l;
    }
} q[MAXN];
inli威力彩開獎時間是幾點ne void add(int x) {
    fz += cnt[x];
    cnt[x]++;
    fm += len;
    len++;
}
inline void del(int x) {
    cnt[x]--;
    fz -= cnt[x];
    len--;
    fm -= len;
}
inline int gcd(int a,int b) {
    return b == 0 ? a : gcd(b,a % b);
}
int main() {
    int g,l=1,r=0;
    scanf("大眾%d%d"大眾,&n,&m);
    B=n/sqrt(m);
    for (int i=1; i<=n; i++)
        scanf("大眾%d"大眾,&col[i]);
    for (int i=0; i<m; i++) {
        scanf("大眾%d%d"大眾,&q[i].l,&q[i].r);
  香港六合彩资料      q[i].id=i;
    }
    sort(q,q+m);
    for (int i=0; i<m; i++) {
        if (q[i].l == q[i].r) {
            ans[q[i].id][0]=0;
            ans[q[i].id][1]=1;
            continue;
        }
        while (l>q[i].l) add(col[--l]);
        while (r<q[i].r) add(col[++r]);
        while (l<q[線上麻將賺錢i].l) del(col[l++]);
        while (r>q[i].r) del(col[r--]);
        g=gcd(fz,fm);
        ans[q[i].id][0]=fz / g;
        ans[q[i].id][1]=fm / g;
    }
    for (int i=0; i<m; i++)
        printf(公眾%d/%d\n公眾,ans[i][0],ans[i][1]);
    return 0;
}

別的例題

小B的扣問

帶修莫隊

后面說過,平凡的莫隊只能辦理沒有點竄的成績,那末帶點竄的成績怎么辦理呢?帶修莫隊便是一種支撐單點點竄的莫隊算法。

算法簡介

仍是對扣問進行排序,每個扣問除了左端點以及右端點還要記載此次扣問是在第幾回點竄以后(時間),以左端點地點塊為第一樞紐字,以右端點地點塊為第二樞紐字,以時間為第三樞紐字進行排序。

暴力查問時,若是當前點竄數比扣問的點竄數少就把沒點竄的進行點竄,反之歸退。

【免責聲明】本站內容轉載自互聯網,其相關談吐僅代表作者小我私家概念盡非權勢巨子,不代表本站態度。如您發明內容存在版權成績,請提交相關鏈接至郵箱:,咱們將實時予以處置。