一直聽說這個神奇的「據說能解決所有區間問題的演算法」,今天學習了一下,可能我只做了幾個模板題,覺得不是很難。
我覺得這個寫的挺好的:點我
莫隊演算法說起來也是暴力,只不過利用了分塊的思想優雅的把n^2變成了n^1.5,太強了,orz。
用乙個小栗子解釋一下分塊的思想:
有一棟樓有100層高,給你兩個雞蛋,讓你最快的找出來雞蛋在那層樓會摔碎(當然是要找第乙個能摔碎的樓層,也就是要找乙個k,滿足雞蛋在k層能摔碎,在第k-1層摔不碎)。應該有很多人的第一想法是二分,當然如果雞蛋是無限個的話肯定是二分最優,但是只有兩個雞蛋,一開始在50層樓摔碎了,你就剩下乙個雞蛋了,只能從第一層到50層挨著試了,如果雞蛋在49樓才會摔碎,豈不是很虧。
這時候就用到分塊的思想了,雖然二分不行,但是我們可以從上面的做法中總結一些經驗,就像剛才說的如果在50樓碎了,傻瓜才會再去51樓試呢,如果50樓不碎相信也沒人再去試49樓了吧,也就是說我們在50樓試之後實際上是把100層樓分成了兩部分。
但是這樣分兩部分的話太不穩定了, 有時候兩次就可以,有時候要50次,那如果我們試著多分幾份呢。想必大家看到這裡也都明白了,分成10份最為穩妥,因為分成aqrt(n) 份,每份有sqrt(n)層樓,這樣期望次數最少。
莫隊演算法也是這樣,把要查詢的區間分成sqrt(n) 份,每份裡面分別暴力,因為裸的暴力複雜度是o(n ^ 2)的,那麼每份裡面的複雜度就是o(n)的了,再加上我們分了sqrt(n)份,總體的複雜度就是o(n * sqrt(n))了。(這個複雜度的證明敢不敢再隨便點啊,丟!)。
咳咳,所以外面就知道莫隊演算法的複雜度大概是o(n * sqrt(n))的了。
然後我在做題的時候發現我做的幾個題可以直接複製過來改改條件就好, 所以我覺得這些模板題裡面可以提純乙個模板出來。那就先來一道最簡單的模板題來開開胃吧
dquery - d-query
題意就是給你乙個序列,然後m組查詢,對於每組查詢l,r;找出區間中數的種類(不同的數的數目)。
翻譯一下題意就是讓你找區間中至少出現一次的數。這個題用線段樹也可以做,但是如果要找至少出現三次的,線段樹就無能為力了吧。
我們先想到o(n^2)的做法
for(i := 1 -> querynumber)
}
妥妥的超時啊,我們來稍稍優化一下。
procedure add(k)
procedure sub(k)
for(i := 1 -> querynumber)
while(l > s[i].l)
while(r > s[i].r)
while(r < s[i].r)
output(ans);
}
(要特別注意上面的--l
和l--
)
這樣的話還是o(n^2)的,但是……神奇的莫隊思想就要出場了,上面我們寫的一點都不用變,只需要改變一下查詢順序,這個o(n^2)就變乘o(n * sqrt(n))了。
怎麼改變順序?
跟我做 :
1 取 t = sqrt(n);上面那道題的ac**2 對查詢區間排序,第一關鍵字按左端點所在塊從小到大,第二關鍵字按右區間從大到小。也就是將排序的判斷條件寫成:
if(a.l / t != b.l / t) return a.l < b.l;
return a.r > b.r;
然後就沒了。
#include
#include
#include
#include
#define mo 1000000007
using
namespace
std;
typedef
long
long ll;
const
int n = 3e5;
ll a[n], sum[n], t;
ll c[n * 10];
struct p s[n];
int cmp(p a, p b)
int main()
t = (int) (sqrt(n) + 0.5);
sort(s + 1, s + m + 1, cmp);
ll ans = 0, l = 0, r = 0;
for(int i = 1; i <= m; i++)
while(l > s[i].l)
while(r > s[i].r)
while(r < s[i].r)
sum[s[i].id] = ans;
}for(int i = 1; i <= m; i++)
printf("%lld\n", sum[i]);
return
0;}
我覺得這就是莫隊的模板了吧。只要不涉及修改,而且在知道一段區間[l, r]的結果之後,能快速求得[l, r + 1]和[l - 1, r]的結果的題,都可以用莫隊來解決。
比如說據說是莫隊演算法的「母體」的《小z的襪子》
嗯……我們剛才說到「知道一段區間[l, r]的結果之後,能快速求得[l, r + 1]和[l - 1, r]的結果」,我們來看一下乙個區間[l, r]內的結果是怎麼來的:
假設乙個區間長度為len區間裡面有n中襪子,每種的數目是ai。那麼就應該是 (c(a1, 2) + c(a2, 2) + ……c(an, 2) ) / c(len, 2);
我們先不管分母,因為分母很好求。只看上面的,假設我們要擴充套件到r + 1, 如果 第 r + 1只襪子是區間[l, r] 裡面的第k中,那麼ak就應該加1了。那麼現在的公式就是 c(a1, 2) + …… + c(ak + 1, 2) + c(a2, 2) + ……c(an, 2) 。其實就是sum[l, r] - c(ak, 2) + c(ak + 1, 2);化簡一下就是 sum[l, r] + ak。這個是可以直接得出來的吧。
**
#include
#include
#include
#define ai s[i]
using
namespace
std;
typedef
long
long ll;
const
int n = 1e5;
ll a[n], c[n], t;
struct ps[n], sum[n];
int cmp(p a, p b)
int main()
for(int i = 1; i <= m; i++)
ll ans = 0;
int l = 0, r = 0;
t = (int) (sqrt(n) + 0.5);
sort(s + 1, s + 1 + m, cmp);
for(int i = 1; i <= m; i++)
while(l < ai.l)
while(r < ai.r)
while(r > ai.r)
sum[ai.id].l = ans;
sum[ai.id].r = (ai.r - ai.l + 1) * (ai.r - ai.l) / 2;
}for(int i = 1; i <= m; i++)
return
0;}
再來一道題吧,其實這是我做出來的第一道莫隊的題。
hysbz - 3289
分析:
首先我們能想得到,如果只能交換相鄰的數,想要將乙個序列排成有序的,那麼步數就是這個序列的逆序對數。假如我們知道乙個序列的逆序對數,如果在它後面放乙個數,就會增加「在這個數前面比他大的數的個數」個逆序對,(哈哈哈,好拗口。)同樣的如果在它前面放乙個數,就會增加「……比它小的數的個數……」個逆序對,知道了這兩個,就相當於知道怎麼從[l, r] 擴充套件到 [l - 1, r] 和 [l, r + 1]了。那麼莫隊走起!利用樹狀陣列可以logn的時間找到比乙個數大或小的個數,記得要離散化
#include
#include
#include
using namespace std;
typedef long long ll;
const int n = 1000005;
struct pa[n], q[n];
ll s[n], c[n], sum[n];
ll t = 0, tot = 0;
int n, m;
int cmp(p a, p b)
int cmp2(p a, p b)
void set(int k, int t)
}ll query(int k)
return ans;
}int main()
sort(q + 1, q + n + 1, cmp2);
for(int i = 1; i <= n; i++)
scanf("%d", &m);
for(int i = 1; i <= m; i++)
t = (int) (sqrt(n) + 0.5);
sort(a + 1, a + 1 + m, cmp);
ll ans = 0;
int countl = 0, countr = 0;
for(int i = 1; i <= m; i++)
while(countl < a[i].l)
while(countr < a[i].r)
while(countr > a[i].r)
sum[a[i].id] = ans;
}for(int i = 1; i <= m; i++)
return
0;}
莫隊演算法學習筆記
莫隊演算法 有時候我們經常會碰到這樣一類問題 給定n和n個數etc,然後給出m組區間詢問 l,r 要求對所有詢問區間給出答案。然後發現這類題通常有乙個很好的性質就是,如果你知道了 l,r 的答案,就可以o 1 或者o lgn 再大就有點玄了 的知道 使得根據第i個區間 li,ri 的答案拓展到第i ...
莫隊演算法學習記錄
可以去這裡找我掛的題,第二第三道要硬拿莫隊做,有點難,不建議寫。以下為學習記錄 昨天補了一道非常接近莫隊思維的題 可以看看我這篇部落格e題然後再來看莫隊就好理解了 之前貼錯鏈結了。已更 例題 cf 617e 題解 a.n的1e5和m的1e5通過莫隊來實現 b.字首異或 include using n...
莫隊演算法學習筆記(一) 普通莫隊
前言 在學習莫隊演算法之前,我一直以為這是乙個很高深的演算法。實際上,它就是乙個很高深的演算法 這個演算法玄學地將分塊與暴力兩大演算法實現了二合一,從而打造出了乙個時間複雜度為o n n o n sqrt n o nn 的求解多個區間詢問的離線演算法。具體介紹 首先,我們以詢問中l ll所在的區間為...