「笔记」莫队算法
写在前面
呃呃呃呃之前学莫队的时候怎么没写博客。
稍微回想了一下,学莫队的时候好像是高一下疫情在家期间,当时天天摆,怪不得没写。
唉,唉,唉!
简单记录下板子。
莫队的时间复杂度证明
抄自:https://www.cnblogs.com/luckyblock/p/13629547.html。
设序列长度为 ,询问次数为 ,假设可以 地处理端点的移动和回答询问。
考虑莫队算法中对询问的排序过程:先按照左端点的块编号升序排序,左端点在同一块中的按右端点升序排序。设块大小为 ,排序后将询问分为了 块,每块内右端点单调递增。
考虑每一块的右端点单调递增,移动的复杂度为 。右端点的垮块移动量上界为 ,则右端点移动的总复杂度为 。对于块内的每一次询问,左端点移动量 。垮块移动左端点的改变量为 ,则左端点移动的总复杂度为 。
算法的总复杂度为 ,由均值不等式,,当且仅当 时,复杂度最低。解得最优块大小为 ,算法总复杂度为 。
如果简单将块大小设为 ,得算法总复杂度为 。与上面得到的最优复杂度作差,得:
考虑括号内的三项,由均值不等式,有:
得块大小设为 劣于块大小为 。
呃呃其实上面的分析只是考虑了运行效率的上界,并不能代表实际运行效率。
为了省事儿块大小一般还是取 。
要是卡不过去那就多调几遍吧!
要是实在卡不过去那就别惦记你那 b 莫队了!
普通莫队
Link:SP3267
询问区间内权值种类数。
用了奇偶块排序优化,不用也罢呃呃。
复制复制// /* By:Luckyblock */ #include <bits/stdc++.h> #define LL long long const int kN = 1e6 + 10; //============================================================= struct Query { int l, r, id; } q[kN]; int n, m, a[kN], bel[kN]; int nowans, nowl = 1, nowr, cnt[kN], ans[kN]; //============================================================= inline int read() { int f = 1, w = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1; for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); return f * w; } bool cmp(Query fir_, Query sec_) { // if (bel[fir_.l] != bel[sec_.l]) return bel[fir_.l] < bel[sec_.l]; // return fir_.r < sec_.r; if (bel[fir_.l] ^ bel[sec_.l]) return bel[fir_.l] < bel[sec_.l]; if (bel[fir_.l] & 1) return fir_.r < sec_.r; return fir_.r > sec_.r; } void add(int pos_) { nowans += !cnt[a[pos_]]; ++ cnt[a[pos_]]; } void del(int pos_) { -- cnt[a[pos_]]; nowans -= !cnt[a[pos_]]; } //============================================================= int main() { // freopen("1.txt", "r", stdin); n = read(); for (int i = 1; i <= n; ++ i) a[i] = read(); m = read(); for (int i = 1; i <= m; ++ i) q[i] = (Query) {read(), read(), i}; for (int i = 1, block = sqrt(n) + 1; i <= n; ++ i) { bel[i] = (i - 1) / block + 1; } std::sort(q + 1, q + m + 1, cmp); for (int i = 1; i <= m; ++ i) { while (nowl < q[i].l) del(nowl), ++ nowl; while (nowl > q[i].l) -- nowl, add(nowl); while (nowr > q[i].r) del(nowr), -- nowr; while (nowr < q[i].r) ++ nowr, add(nowr); ans[q[i].id] = nowans; } for (int i = 1; i <= m; ++ i) printf("%d\n", ans[i]); return 0; }
带修莫队
Link:P1903
单点修改,询问区间内权值种类数。
块长取 ,不会证。
// /* By:Luckyblock https://www.luogu.com.cn/problem/P1903 */ #include <bits/stdc++.h> #define LL long long const int kN = 1e6 + 10; //============================================================= struct Query { int l, r, time, id; } q[kN]; struct Change { int pos, val; } c[kN]; int n, m, qnum, cnum, a[kN], bel[kN]; int nowans, nowl = 1, nowr, nowt, cnt[kN], ans[kN]; //============================================================= inline int read() { int f = 1, w = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1; for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); return f * w; } bool cmp(Query fir_, Query sec_) { if (bel[fir_.l] != bel[sec_.l]) return bel[fir_.l] < bel[sec_.l]; if (bel[fir_.r] != bel[sec_.r]) return bel[fir_.r] < bel[sec_.r]; return fir_.time < sec_.time; } void add(int pos_) { nowans += !cnt[a[pos_]]; ++ cnt[a[pos_]]; } void del(int pos_) { -- cnt[a[pos_]]; nowans -= !cnt[a[pos_]]; } void change(int now_, int L_, int R_) { if (L_ <= c[now_].pos && c[now_].pos <= R_) del(c[now_].pos); std::swap(a[c[now_].pos], c[now_].val); if (L_ <= c[now_].pos && c[now_].pos <= R_) add(c[now_].pos); } //============================================================= int main() { // freopen("1.txt", "r", stdin); n = read(), m = read(); for (int i = 1; i <= n; ++ i) a[i] = read(); for (int i = 1; i <= m; ++ i) { char opt[5]; scanf("%s", opt + 1); if (opt[1] == 'Q') q[++ qnum] = (Query) {read(), read(), cnum, qnum}; if (opt[1] == 'R') c[++ cnum] = (Change) {read(), read()}; } for (int i = 1, block = pow(n, 2.0 / 3.0); i <= n; ++ i) { bel[i] = (i - 1) / block + 1; } std::sort(q + 1, q + qnum + 1, cmp); for (int i = 1; i <= qnum; ++ i) { while (nowl < q[i].l) del(nowl), ++ nowl; while (nowl > q[i].l) -- nowl, add(nowl); while (nowr > q[i].r) del(nowr), -- nowr; while (nowr < q[i].r) ++ nowr, add(nowr); while (nowt < q[i].time) ++ nowt, change(nowt, q[i].l, q[i].r); while (nowt > q[i].time) change(nowt, q[i].l, q[i].r), -- nowt; ans[q[i].id] = nowans; } for (int i = 1; i <= qnum; ++ i) printf("%d\n", ans[i]); return 0; }
回滚莫队
Link:AT_joisc2014_c
用于处理难以维护在莫队时删除元素的问题。
先分块,再枚举左端点所在块,每次询问如果左右端点在同一块中暴力,否则正常莫队,但是每次都重置左端点为该块的右端点并重置答案。
莫队的区间只会扩张而不会缩短。
// /* By:Luckyblock https://www.luogu.com.cn/problem/AT_joisc2014_c */ #include <bits/stdc++.h> #define LL long long const int kN = 2e5 + 10; const int kBlock = kN; //============================================================= int n, m, datanum, a[kN], data[kN]; int block, blocknum, bel[kN], L[kBlock], R[kBlock]; int nowl, nowr, cnt1[kN], cnt2[kN]; LL nowans, temp, ans[kN]; struct Query { int l, r, id; } q[kN]; //============================================================= inline int read() { int f = 1, w = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1; for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); return f * w; } bool cmp(Query fir_, Query sec_) { if (bel[fir_.l] != bel[sec_.l]) return bel[fir_.l] < bel[sec_.l]; return fir_.r < sec_.r; } void Init() { n = read(), m = read(); for (int i = 1; i <= n; ++ i) a[i] = data[i] = read(); std::sort(data + 1, data + n + 1); datanum = std::unique(data + 1, data + n + 1) - data - 1; //-1 for (int i = 1; i <= n; ++ i) { a[i] = std::lower_bound(data + 1, data + datanum + 1, a[i]) - data; } block = sqrt(n), blocknum = ceil(1.0 * n / block); for (int i = 1; i <= blocknum; ++ i) { L[i] = (i - 1) * block + 1; R[i] = i * block; } R[blocknum] = n; for (int i = 1; i <= blocknum; ++ i) { for (int j = L[i]; j <= R[i]; ++ j) { bel[j] = i; } } for (int i = 1; i <= m; ++ i) q[i] = (Query) {read(), read(), i}; std::sort(q + 1, q + m + 1, cmp); } void add(int pos_) { ++ cnt1[a[pos_]]; nowans = std::max(nowans, 1ll * cnt1[a[pos_]] * data[a[pos_]]); } //============================================================= int main() { // freopen("1.txt", "r", stdin); Init(); int p = 1; for (int i = 1; i <= blocknum; ++ i) { nowl = R[i] + 1, nowr = R[i], nowans = 0; for (int j = 1; j <= datanum; ++ j) cnt1[j] = 0; for (; bel[q[p].l] == i && p <= m; ++ p) { int ql = q[p].l, qr = q[p].r, id = q[p].id; if (bel[ql] == bel[qr]) { for (int j = ql; j <= qr; ++ j) cnt2[a[j]] = 0; for (int j = ql; j <= qr; ++ j) { ++ cnt2[a[j]]; ans[id] = std::max(ans[id], 1ll * cnt2[a[j]] * data[a[j]]); } continue; } while (nowr < qr) ++ nowr, add(nowr); temp = nowans; while (nowl > ql) -- nowl, add(nowl); ans[id] = nowans; while (nowl < R[i] + 1) -- cnt1[a[nowl]], ++ nowl; nowans = temp; } } for (int i = 1; i <= m; ++ i) printf("%lld\n", ans[i]); return 0; }
树上莫队
Link:SP10707 COT2 - Count on a tree II
求欧拉序,转成序列问题。
注意判断 是否在 的子树中,两种情况询问区间不同,如果不在还需要额外统计 lca 的贡献。
// /* By:Luckyblock https://www.luogu.com.cn/problem/SP10707 */ #include <bits/stdc++.h> #define LL long long const int kN = 1e6 + 10; //============================================================= struct Query { int l, r, bell, belr, id, lca; } q[kN]; int n, m, datanum, a[kN], data[kN]; int edgenum, head[kN], v[kN << 1], ne[kN << 1]; int fa[kN], sz[kN], dep[kN], son[kN], top[kN]; int block, dfnnum, s[kN], t[kN], b[kN << 1]; int nowl = 1, nowr, nowans, cnt[kN], ans[kN]; bool used[kN]; //============================================================= inline int read() { int f = 1, w = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1; for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); return f * w; } void Add(int u_, int v_) { v[++ edgenum] = v_; ne[edgenum] = head[u_]; head[u_] = edgenum; } namespace Cut { void Dfs1(int u_, int fa_) { sz[u_] = 1; fa[u_] = fa_; dep[u_] = dep[fa_] + 1; s[u_] = ++ dfnnum, b[dfnnum] = u_; for (int i = head[u_]; i; i = ne[i]) { int v_ = v[i]; if (v_ == fa_) continue; Dfs1(v_, u_); sz[u_] += sz[v_]; if (sz[v_] > sz[son[u_]]) son[u_] = v_; } t[u_] = ++ dfnnum, b[dfnnum] = u_; } void Dfs2(int u_, int top_) { top[u_] = top_; if (son[u_]) Dfs2(son[u_], top_); for (int i = head[u_]; i; i = ne[i]) { int v_ = v[i]; if (v_ == fa[u_] || v_ == son[u_]) continue; Dfs2(v_, v_); } } int Lca(int u_, int v_) { for (; top[u_] != top[v_]; u_ = fa[top[u_]]) { if (dep[top[u_]] < dep[top[v_]]) std::swap(u_, v_); } return dep[u_] < dep[v_] ? u_ : v_; } } bool cmp(Query fir_, Query sec_) { if (fir_.bell != sec_.bell) return fir_.bell < sec_.bell; return fir_.r < sec_.r; } void Init() { n = read(), m = read(); for (int i = 1; i <= n; ++ i) a[i] = data[i] = read(); std::sort(data + 1, data + n + 1); datanum = std::unique(data + 1, data + n + 1) - data + 1; for (int i = 1; i <= n; ++ i) { a[i] = std::lower_bound(data + 1, data + datanum + 1, a[i]) - data; } for (int i = 1; i < n; ++ i) { int u_ = read(), v_ = read(); Add(u_, v_), Add(v_, u_); } Cut::Dfs1(1, 0), Cut::Dfs2(1, 1); block = n * 2 / sqrt(m * 2 / 3); for (int i = 1; i <= m; ++ i) { int u_ = read(), v_ = read(), lca = Cut::Lca(u_, v_); if (s[u_] > s[v_]) std::swap(u_, v_); if (lca == u_) { q[i] = (Query) {s[u_], s[v_], s[u_] / block, s[v_] /block, i, 0}; } else { q[i] = (Query) {t[u_], s[v_], t[u_] / block, s[v_] / block, i, lca}; } } std::sort(q + 1, q + m + 1, cmp); } void add(int now_) { nowans += !cnt[a[now_]]; ++ cnt[a[now_]]; } void del(int now_) { -- cnt[a[now_]]; nowans -= !cnt[a[now_]]; } void calc(int now_) { if (!used[now_]) add(now_); else del(now_); used[now_] ^= 1; } //============================================================= int main() { // freopen("1.txt", "r", stdin); Init(); for (int i = 1; i <= m; ++ i) { while (nowl > q[i].l) -- nowl, calc(b[nowl]); while (nowl < q[i].l) calc(b[nowl]), ++ nowl; while (nowr < q[i].r) ++ nowr, calc(b[nowr]); while (nowr > q[i].r) calc(b[nowr]), -- nowr; if (q[i].lca) calc(q[i].lca); ans[q[i].id] = nowans; if (q[i].lca) calc(q[i].lca); } for (int i = 1; i <= m; ++ i) printf("%d\n", ans[i]); return 0; }
写在最后
呃呃。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】