莫隊演算法整理

2022-05-30 05:21:06 字數 3874 閱讀 9721

首先拿到這道題目可以想到的最暴力的解法,遍歷所有區間[l,r],用cnt陣列記錄每個陣列出現的次數,最後統計出現數字的種類,這樣的時間複雜度就是o(m * (n + maxai)),肯定是過不了題的。

然後莫隊演算法這種通過優美優化之後的暴力演算法就能解決這種問題,莫隊演算法就是通過分塊思想,將查詢區間進行排序,將時間複雜度優化到o(n * sqrt(n))

既然需要排序,那麼需要了解莫隊是怎麼排序的:

首先將整個序列分為sqrt(n)塊,每塊長度為sqrt(n),並按照1到sqrt(n)進行標號。這裡假設len = sqrt(n)。然後向詢問的區間進行排序,以區間左端點所在的塊的序號進行排序,如果所在塊的需要相同,則再按照右端點從小到大進行排序。

然後分析一下所需要的時間複雜度:

(1)sort的時間複雜度:n * log(n)

(2)因為區間左端點是按照塊的序號進行排序的,而塊內的左端點實際上是無序的,這裡考慮他的最大時間複雜度,即每一次移動都從塊的一端移動到另一端,那麼m個端點所需要的複雜度就是o(m * len)。

(3)在區間左端點處於同一塊的時候,區間的右端點是有序的,同樣考慮他的最大時間複雜度,即每一次移動都從序列的最左端移動到序列的最右端,因為一共有len個塊,所需要的複雜度就是o(len * n)

這樣一來總的時間複雜度就優化到了o(n * log(n) + m * sqrt(n) + n * sqrt(n)) = o(n * sqrt(n))

標準的排序**

int cmp(node& a, node&b)

但是當實際運用莫隊演算法的時候,因為莫隊演算法的複雜度本身就是o(n * sqrt(n))在一些情況下,極其容易被卡常數。

這裡就需要許多優(shen)美(xian)的優化

(這個排序真的是優(shen)美(xian))

最最最最重要的優化!!!

看似沒有用,但是對於每個塊的查詢都可以減少很多時間

int cmp(node& a, node&b)

對於奇數塊右端點從小到大排序,偶數塊右端點從大到小排序。

原理其實很簡單:右指標跳完奇數塊,往回跳的時候,順便把偶數塊跳完,然後再跳下一塊奇數塊

就是把這部分

void add(int

pos)

void del(int

pos)

和這部分

while(l < ql) del(l++);

while(l > ql) add(--l);

while(r < qr) add(++r);

while(r > qr) del(r--);

根據運算子的優先順序生生壓縮成

while(l < ql) now -= !--cnt[aa[l++]];

while(l > ql) now += !cnt[aa[--l]]++;

while(r < qr) now += !cnt[aa[++r]]++;

while(r > qr) now -= !--cnt[aa[r--]];

(垃圾函式呼叫,我選擇inline)

如果實在被卡到落淚,然後又寫不出其他演算法可以一試(當然如果比賽已經把這個禁了,那就不要增加罰時了)

#include #include 

#include

#include

#define isdigit(x) ((x) >= '0' && (x) <= '9')

#define maxn 1000010

using

namespace

std;

intread()

while (isdigit(ch)) x = (x << 3) + (x << 1) + (ch ^ 48), ch =getchar();

return w ? -x : x;

}int

n, m;

int arr[maxn], cnt[maxn], res = 0

, pos[maxn] , ans[maxn];

intsize, bnum;

struct

node

q[maxn];

int cmp1(node & a, node &b)

void add(int

x) void del(int

x)void printi(int

x) int

main()

m =read();

for (int i = 1; i <= m; ++i)

sort(q + 1, q + 1 +m, cmp1);

int l = 1, r = 0

;

for (int i = 1; i <= m; ++i)

for (int i = 1; i <= m; ++i)

return0;

}

ac**

建議手動交c++11,被ce到哭

#include#include

#include

#include

#include

#include

#include

#include

#include

#include

#define ios ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)

#define mem(a,x) memset(a,x,sizeof(a))

#define lson rt<<1,l,mid

#define rson rt<<1|1,mid + 1,r

#define p pair#define ull unsigned long long

using

namespace

std;

typedef

long

long

ll;const

int maxn = 5e5 + 10

;const ll mod = 998244353

;const

int inf = 0x3f3f3f3f

;const

long

long inf = 0x3f3f3f3f3f3f3f3f

;const

double eps = 1e-7

;inline ll read()

while (isdigit(ch)) x = (x << 3) + (x << 1) + (ch ^ 48), ch =getchar();

return w ? -x : x;

}ll n, m, k;

ll arr[maxn], pos[maxn], res = 0, cnt[maxn] = , ans[maxn];

struct

node

q[maxn];

int cmp(node& a, node&b)

void add(int

x) void del(int

x)int

main()

for (int i = 1; i <= n; ++i)

for (int i = 1; i <= m; ++i)

//cout << "debug" << endl;

sort(q + 1, q + 1 +m, cmp);

int l = 1, r = 0

;

for (int i = 1; i <= m; ++i)

for (int i = 1; i <= m; ++i)

cout

<< ans[i] <

return0;

}

ac**

待補充....

樹上莫隊演算法

繼續回來寫部落格 記錄點有意思的題目什麼的。貌似寫過這個的沒多少人 所以我也記錄一點。首先序列上的莫隊大家都應該很熟悉了 那麼樹上的莫隊要怎麼搞呢?先來看個題目 spoj cot2 求樹上兩點間路徑上有多少個不同的點權。序列上的莫隊是把詢問按照左端點分塊了 可是樹上沒有左端點,怎麼辦呢?我們把樹分塊...

模板 莫隊演算法

題意 給定乙個大小為n的陣列,陣列中所有元素的大小 n。你需要回答m個查詢。每個查詢的形式是l,r,k。你需要回答在範圍 l,r 中至少重複k次的數字的個數。n,m 100000 誒,這題卡了好久,tle,中間棄了一段,然後今天學弟學莫隊,拿出這個題,他也沒什麼想法,然後我頓時退一步海闊天空了。最開...

模板 莫隊演算法

這個演算法是由之前的國家隊隊長莫濤巨神 orz 發明的,所以尊稱莫隊演算法。如果我們知道區間 l,r 就能在o 1 求出 l 1,r l 1,r l,r 1 l,r 1 的話,那就可以用莫隊演算法了。1 排序,以左段點所在的塊為第一關鍵字,以右端點為第二關鍵字 2 從左往右處理詢問 離線 3 不斷調...