莫隊算法

滿足以下兩個條件的即可使用莫隊算法:

1. 只有查詢沒有修改

2. 可由[l, r] O(1)或O(lgn) 推出[l + 1, r], [l - 1, r], [l, r - 1], [l, r + 1]

複雜度即爲O(n ^ 1.5)或O(n ^ 1.5 * lgn)


莫隊算法是將詢問按左端點所在塊作爲第一關鍵字,右端點作爲第二關鍵字排序。

莫隊算法非常容易實現,只需排好序順着計算就好了。關鍵是複雜度證明。


先大致說下實現過程。

例題: bzoj2038 [2009國家集訓隊] 小z的襪子(house)

題目大意:

長度爲n的序列代表n只襪子,每隻襪子有顏色ci(不分左右,型號,只關注顏色),m個詢問,詢問區間內抽到一雙相同顏色襪子的概率。

題解:

無查詢;記錄區間內抽到一雙相同顏色襪子的排列數,如果知道[l, r]的答案,

可以通過nowans -= cnt[r + 1] * (cnt[r + 1] - 1);++cnt[r + 1]; nowans += cnt[r + 1] * (cnt[r + 1] - 1); O(1)求得[l, r + 1]的答案。[l + 1, r], [l - 1, r], [l, r - 1]同理。其中cnt[x]表示當前區間內顏色x的個數。

故可使用莫隊。

假設一開始我們找到一個區間的答案(比如[1, 0])。對於m個詢問,我們按莫隊算法描述的順序排序,按照這個順序依次用第i個答案推出第i+1個答案。每一步操作數都是| l' - l | + | r' - r |。

主要代碼:

void updata(int x, int y) {
    now -= (ll)cnt[x] * (cnt[x] - 1);
    cnt[x] += y;
    now += (ll)cnt[x] * (cnt[x] - 1);
}

for (int i = 0, l = 1, r = 0; i < m; ++i) {
    while (r < q[i].r) updata(c[++r], 1);
    while (r > q[i].r) updata(c[r--], -1);
    while (l > q[i].l) updata(c[--l], 1);
    while (l < q[i].l) updata(c[l++], -1);
    if (now == 0) {
        a[q[i].id] = 0;
        b[q[i].id] = 1;
    }
    else {
        a[q[i].id] = now;
        b[q[i].id] = (ll)(q[i].r - q[i].l + 1) * (q[i].r - q[i].l);
        ll tmp = __gcd(a[q[i].id], b[q[i].id]);
        a[q[i].id] /= tmp;
        b[q[i].id] /= tmp;
    }
}
下面給出粗略證明。由於n, m同一級別,所以計算複雜度時都用n來表示。


總共分成了n ^ 0.5塊,我們不妨假設這n ^ 0.5塊每塊的第一個都是暴力算出來的。暴力算一個區間[l, r]是O(n),總共是O(n ^ 1.5)。

然後在每塊內分別考慮左端點和右端點。

左端點:

同一塊之間左端點相差不超過n ^ 0.5,所以每次最多移動n ^ 0.5次,總共需計算n個詢問,複雜度是O(n ^ 1.5)。

右端點:

在同一塊裏,右端點遞增,最多移動n次,總共有n ^ 0.5塊, 所以移動n ^ 1.5次。複雜度也是O(n ^ 1.5)。

綜上,總複雜度O(n ^ 1.5)。


練習題:hdu5145 NPY and girls

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章