滿足以下兩個條件的即可使用莫隊算法:
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