前言
莫隊是由莫濤提出的一種離線演算法,是分塊與雙指標的結合,一般可以在 \(O(n \sqrt n)\) 或者 \(O(n \sqrt m)\) 的複雜度解決一些種類問題。
普通莫隊
SP3267 DQUERY - D-query
給你一個長度為 \(n\) 的數列 \(A\),\(m\) 次詢問,每次詢問區間 \([l,r]\) 內的不同數字的個數。
如果要線上的話,需要用主席樹。
我們考慮離線,把每個操作離線下來,然後掛到序列上。
每一個顏色代表一個詢問。
可以想到用雙指標來做,代表區間 \([l, r]\),如果任意指標移動就更新答案,是 \(O(1)\) 的。
想象是美好的,現實是殘酷的。
如果詢問是這樣子的話:
那麼儘管排完序左端點的移動可以做到 \(O(n)\),但是啊但是,右端點是無序的,就又會把複雜度打會 \(O(nm)\)。
那麼這時候,莫隊誕生了。
莫隊在原來的基礎上加了一個分塊與排序。
可以證明覆雜度是 \(O(n\sqrt n)\) 的。
對於左端點,如果每一個塊中有 \(x\) 個詢問的左端點,那麼對於這個塊的最壞複雜度是 \(O(x_i\sqrt n)\)。對於整個塊,跨越一次需要 \(O(\sqrt n)\),最壞會跨整個序列,也就是 \(O(n)\),那麼複雜度就是 $ O (\sum x_i\sqrt n + n\sqrt n) = O (n \sqrt n)$。
對於右端點,因為已經排完序,最壞需要跨整個序列,也就是 \(O(n)\),有 \(O(\sqrt n)\) 塊,所以複雜度就是 \(O(n \sqrt n)\)。
所以總複雜度就是 \(O(n \log n + n \sqrt n + n \sqrt n) = O(n \sqrt n)\)。
回到這題,我們已經可以基本寫出來了。
int n, m, len, tot;
int a[N], cnt[V];
bool cmp (Query A, Query B) { return A.x / len == B.x / len ? (A.y == B.y ? 0 : ((A.x / len) & 1) ^ (A.y < B.y)) : A.x < B.x; }
bool Cmp (Query A, Query B) { return A.id < B.id; } // 這裡用了兩個 cmp,實際不需要,用一個 ans 陣列記錄即可。
// 移動指標的修改
void add (int x) {
if (!cnt[a[x]]) tot++;
cnt[a[x]]++;
}
void del (int x) {
if (cnt[a[x]] == 1) tot--;
cnt[a[x]]--;
}
int main () {
len = sqrt (n); // 這句話千萬別漏了
sort (q + 1, q + m + 1, cmp);
for (int i = 1, l = 1, r = 0; i <= m; ++i) {
while (l > q[i].x) add (--l);
while (r < q[i].y) add (++r);
while (l < q[i].x) del (l++);
while (r > q[i].y) del (r--);
q[i].ans = tot;
}
sort (q + 1, q + m + 1, Cmp);
return 0;
}
帶修莫隊
P1903 [國家集訓隊] 數顏色 / 維護佇列
給你一個長度為 \(n\) 的序列 \(A\) 以及 \(m\) 個操作,要你支援單點修改,並且詢問區間顏色種類數。
現在帶修了(惱)。
我們可以把每個詢問抽象成 \((l, r)\) 的一個點對,這樣我們可以構建出一個二維空間。
我們不妨把每一個操作用時間戳來表示,這樣我們就可以多一個維度 \(t\),用來記錄每次詢問的時間維度。
初始化時,只需要找到最近的那個時間維度即可。
再來考慮考慮這個維度該怎麼轉移。
我們假設當前這個區間已經修改了 \(x\) 次,要轉移到修改 \(y\) 次的序列。
如果 \(x \lt y\),那麼我們需要把 \(x + 1\) 到 \(y\) 個修改全都加上。
如果 \(x \gt y\),那麼我們需要把 \(x\) 到 \(y + 1\) 個修改全部還原。
什麼?你問 \(x = y\)?
這種情況還需要轉移嗎。。。
於是我們在原來莫隊的基礎上加了一維,成功地實現了帶修莫隊。
int n, m, len, qcnt, rcnt;
int a[N], ans[N], cnt[V], tot;
struct Query {
int id, l, r, t;
bool operator < (const Query &T) {
if (l / len != T.l / len) return l < T.l;
if (r / len != T.r / len) return r < T.r;
return t < T.t;
}
} q[N];
void add (int x) {
if (!cnt[x]) tot++;
cnt[x]++;
}
void del (int x) {
if (cnt[x] == 1) tot--;
cnt[x]--;
}
int main () {
for (int i = 1; i <= m; ++i) {
char opt;
int x, y;
cin >> opt >> x >> y;
if (opt == 'Q') q[++qcnt] = (Query){qcnt, x, y, rcnt};
else r[++rcnt] = (Modify){x, y};
}
len = pow (n, 2.0 / 3.0);
int L = 1, R = 0, now = 0;
sort (q + 1, q + qcnt + 1);
for (int i = 1; i <= qcnt; ++i) {
while (L > q[i].l) add (a[--L]);
while (R < q[i].r) add (a[++R]);
while (L < q[i].l) del (a[L++]);
while (R > q[i].r) del (a[R--]);
while (now < q[i].t) {
now++;
if (L <= r[now].x && r[now].x <= R) {
add (r[now].y);
del (a[r[now].x]);
}
swap (a[r[now].x], r[now].y);
}
while (now > q[i].t) {
if (L <= r[now].x && r[now].x <= R) {
add (r[now].y);
del (a[r[now].x]);
}
swap (a[r[now].x], r[now].y);
now--;
}
ans[q[i].id] = tot;
}
return 0;
}
樹上莫隊
眾所周知,莫隊是用於序列上的一種演算法,如果要用到樹上,我們最先想到的就是把樹序列化。
樹上莫隊?
但是一般的 DFS 序這樣肯定是不行的,畢竟要方便統計路徑上權值的種類數。
但是尤拉序很好的滿足了這個要求。
如圖,可以發現尤拉序只有標紅圈的節點沒有算上,而這個標紅圈的節點正是 \(2\) 與 \(6\) 的 Lca。
所以我們只需要在轉移過程中特判一下即可。
SP10707 COT2 - Count on a tree II
給你一棵 \(n\) 個節點的樹,每次詢問路徑 \(u\) 到 \(v\) 上的權值種類數。
典,直接上。
const int N = 2e5 + 5;
int n, m, len;
int dep[N], fa[N][31], lg[N], eu[N], ucnt;
int fi[N], ls[N];
void dfs (int now, int fath){
fa[now][0] = fath;
dep[now] = dep[fath] + 1;
eu[++ucnt] = now;
fi[now] = ucnt;
for (int i = 1; i < 31; ++i){
fa[now][i] = fa[fa[now][i-1]][i-1];
}
for (int i = head[now]; i; i = e[i].nxt)
if (e[i].v != fath)
dfs(e[i].v, now);
eu[++ucnt] = now;
ls[now] = ucnt;
}
int Lca (int x, int y)
void modify (int x) {
if (!vis[x]) {
if (!cnt[a[x]]) tot++;
cnt[a[x]]++;
} else {
if (cnt[a[x]] == 1) tot--;
cnt[a[x]]--;
}
vis[x] ^= 1;
}
int main () {
ios::sync_with_stdio (false);
cin.tie (0); cout.tie (0);
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
b[i] = a[i];
}
for (int i = 1; i < n; ++i) {
int u, v;
cin >> u >> v;
addEdge (u, v);
addEdge (v, u);
}
sort (b + 1, b + n + 1);
int ret = unique (b + 1, b + n + 1) - b - 1;
for (int i = 1; i <= n; ++i) a[i] = lower_bound (b + 1, b + n + 1, a[i]) - b;
len = sqrt (n);
for (int i = 1; i <= m; ++i) {
int x, y;
cin >> x >> y;
if (fi[x] > fi[y]) swap (x, y);
q[i].id = i, q[i].lca = Lca (x, y);
if (q[i].lca == x) {
q[i].x = fi[x];
q[i].y = fi[y];
q[i].lca = 0;
} else {
q[i].x = ls[x];
q[i].y = fi[y];
}
}
sort (q + 1, q + m + 1);
int l = 1, r = 0;
for (int i = 1; i <= m; ++i) {
while (l > q[i].x) modify (eu[--l]);
while (r < q[i].y) modify (eu[++r]);
while (l < q[i].x) modify (eu[l++]);
while (r > q[i].y) modify (eu[r--]);
if (q[i].lca) modify (q[i].lca); // 注意特判
ans[q[i].id] = tot;
if (q[i].lca) modify (q[i].lca);
}
return 0;
}