当前位置: 首页 > ds >正文

莫队算法学习笔记

莫队算法

2025-04-28

莫队算法是一种处理区间询问的离线算法, 通过分块可以达到 n n n\sqrt{n} nn 的时间复杂度。

最基础的莫队算法是, 把长度为 n n n 的区间, 分成大小为 n \sqrt{n} n 的块, 然后把询问的 [ l , r ] [l,r] [l,r] 先按照 l l l 所在的块的位置排序, 再按 r r r 的大小排序。

我们定义 L , R L, R L,R 用来记录当前维护的答案的区间, 只需要根据当前需要查询的区间动态地去移动 L L L R R R 。 所以要使用莫队,需要保证能从 [ l , r ] [l, r] [l,r] 的贡献推出 [ l , r + 1 ] [l, r+1] [l,r+1] 的贡献。

莫队也算是一种暴力的算法, 但是时间复杂度却可以做到 n n n \sqrt{n} nn 。在一个块内, L L L 最多只会移动 n n \sqrt {n} \sqrt{n} n n 次, R R R 最多只会移动 n \sqrt{n} n 次,而总共只有 n \sqrt{n} n 块,所以总体操作的次数最多会是 n n + n n\sqrt{n} + n nn +n 次。

普通的

基本会是比较板的,直接写就行。

P2709 小B的询问

比较模板的题,不需要做什么改变。

struct node {int l, r, idx;
};
void ChatGptDeepSeek() // Date: 2025-04-25
{ // Time: 15:33:26int n, m, k;cin >> n >> m >> k;vi a(n + 1), c(k + 1), ans(m), pos(n + 1);int siz = sqrt(n);for (int i = 1; i <= n; i++)cin >> a[i], pos[i] = i / siz;vector<node> Q(m);for (int i = 0; i < m; i++)cin >> Q[i].l >> Q[i].r, Q[i].idx = i;sort(all(Q), [&](node x, node y) {return (pos[x.l] == pos[y.l]) ? (x.r < y.r) : (pos[x.l] < pos[y.l]);});int L = 1, R = 0;ll res = 0;auto add = [&](int i) {assert(i > 0 && i <= n);res -= (ll)c[a[i]] * c[a[i]];c[a[i]]++;res += (ll)c[a[i]] * c[a[i]];};auto sub = [&](int i) {assert(i > 0 && i <= n);res -= (ll)c[a[i]] * c[a[i]];c[a[i]]--;res += (ll)c[a[i]] * c[a[i]];};for (int i = 0; i < m; i++) {auto [l, r, idx] = Q[i];while(L > l) add(--L);while(R < r) add(++R);while(L < l) sub(L++);while(R > r) sub(R--);// cerr << L << " " << R << '\n';ans[idx] = res;}for(int i = 0; i < m; i++)cout << ans[i] << "\n";
}

CF86D

做 CF 按 AC 数排序的第一题, 然后感觉不会, 一看题解说要用莫队。 就是从这开始想着要学一下莫队的。

也是只涉及基本的操作的一题。

但是这题也有些帮助, 用 vector 装结构体的提交是 2062ms , 仅仅把 vector 换成数组, 时间就变成了 1280ms , 在数据量较大的情况下, 差异还是挺大的。

constexpr int N = int(1e6)+5;
int cnt[N];
struct node{int l, r, idx;
};
node Q[N];
void ChatGptDeepSeek() // Date: 2025-04-25
{                      // Time: 21:27:09 int n, q;cin >> n >> q;vi a(n + 1), pos(n + 1);vl ans(q + 1);int siz = sqrt(n);for(int i = 1; i <= n; i++)cin >> a[i], pos[i] = i / siz;// vector<node>Q(q);for(int i = 0; i < q; i++){cin >> Q[i].l >> Q[i].r;Q[i].idx = i;}sort(Q, Q + q, [&](node x, node y){return (pos[x.l] == pos[y.l]) ? (x.r < y.r) : (pos[x.l] < pos[y.l]);});// sort(all(Q), [&](node x, node y){//     return (pos[x.l] == pos[y.l]) ? (x.r < y.r) : (pos[x.l] < pos[y.l]);// });ll res = 0;auto add = [&](int i){res -= (ll)cnt[a[i]] * cnt[a[i]] * a[i];cnt[a[i]]++;res += (ll)cnt[a[i]] * cnt[a[i]] * a[i];};auto sub = [&](int i){res -= (ll)cnt[a[i]] * cnt[a[i]] * a[i];cnt[a[i]]--;res += (ll)cnt[a[i]] * cnt[a[i]] * a[i];};int L = 1, R = 0;for(int i = 0; i < q; i++){auto [l, r, idx] = Q[i];while(L > l) add(--L);while(R < r) add(++R);while(L < l) sub(L++);while(R > r) sub(R--);ans[idx] = res;}for(int i = 0; i < q; i++)cout << ans[i] << "\n";
}

P1494 [国家集训队] 小 Z 的袜子

比较板的题。 std::format 真好用hhh, 不过得 C++ 20 才能用。
求一下区间的取到两个相同数字的方式的数量,和总数量取一下 gcd 就行。

constexpr int N = int(5e4)+5;
struct node{int l, r, idx;
};
node Q[N];
void ChatGptDeepSeek() // Date: 2025-04-26
{                      // Time: 00:48:12 /*洛谷P1494询问区间的抽到两个相同数字的概率*/int n, m;cin >> n >> m;int siz = sqrt(n);vi c(n + 1), pos(n + 1), cnt(n + 1);for(int i = 1; i <= n; i++)cin >> c[i], pos[i] = i / siz;for(int i = 0; i < m; i++){cin >> Q[i].l >> Q[i].r;Q[i].idx = i;}sort(Q, Q + m, [&](node x, node y){return (pos[x.l] == pos[y.l]) ? (x.r < y.r) : (pos[x.l] < pos[y.l]);});ll res = 0;auto add = [&](int i){res -= (ll)cnt[c[i]] * (cnt[c[i]] - 1) / 2;cnt[c[i]]++;res += (ll)cnt[c[i]] * (cnt[c[i]] - 1) / 2;};auto sub = [&](int i){res -= (ll)cnt[c[i]] * (cnt[c[i]] - 1) / 2;cnt[c[i]]--;res += (ll)cnt[c[i]] * (cnt[c[i]] - 1) / 2;};int L = 1, R = 0;vector<string> ans(m);for(int i = 0; i < m; i++){auto [l, r, idx] = Q[i];while(L > l) add(--L);while(R < r) add(++R);while(L < l) sub(L++);while(R > r) sub(R--);ll X = (ll)(R - L + 1) * (R - L) / 2;ll g = __gcd(X, res);// cout << res / g << "/" << X / g << '\n';if(g == 0) ans[idx] = "0/1";elseans[idx] = format("{}/{}",res/g,X/g);}for(int i = 0; i < m; i++)cout << ans[i] << '\n';
}

P4462 [CQOI2018] 异或序列

这题没想出来, 还是挺有细节的。

查询的区间 [ l , r ] [l, r] [l,r] 直接给处理成 [ l − 1 , r ] [l - 1, r] [l1,r], 查询区间 [ l , r ] [l, r] [l,r] 的值其实是问是否存在 [ l − 1 , r ] [l-1, r] [l1,r] 之间的两个前缀使得 s x ⊕ s y = k s_x \oplus s_y = k sxsy=k

加减也是需要注意一些东西。

struct node{int l, r, idx;
};
node Q[100001];
int cnt[200002];
void ChatGptDeepSeek() // Date: 2025-04-26
{                      // Time: 20:37:22 int n, m, k;cin >> n >> m >> k;int siz = sqrt(n);vector<int> a(n + 1), pos(n + 1), pre(n + 1);vl ans(m);for(int i = 1; i <= n; i++){cin >> a[i];pre[i] = pre[i - 1] ^ a[i];pos[i] = i / siz;}for(int i = 0; i < m; i++){cin >> Q[i].l >> Q[i].r;Q[i].idx = i;}sort(Q, Q + m, [&](node x, node y){return (pos[x.l] == pos[y.l]) ? (x.r < y.r) : (pos[x.l] < pos[y.l]);});ll res = 0;auto add = [&](int i){res += cnt[pre[i] ^ k];cnt[pre[i]]++;};auto sub = [&](int i){cnt[pre[i]]--;res -= cnt[pre[i] ^ k];};cnt[0] = 1;int L = 0, R = 0;for(int i = 0; i < m; i++){auto [l, r, idx] = Q[i];--l;while(L > l) add(--L);while(R < r) add(++R);while(L < l) sub(L++);while(R > r) sub(R--);ans[idx] = res;}for(int i = 0; i < m; i++)cout << ans[i] << '\n';
}

带修莫队

带修莫队目前只写了一题, 后面会更新。

如果是带修的莫队, 一般会先按 l l l 的块的顺序排, 再按 r r r 的顺序排, 再按 t t t 的大小来排, t t t 表示的是前面的修改的次数, 块的大小一般选择 n 2 3 \sqrt[3]{n^2} 3n2 , 也就是 n 2 3 n^{\frac{2}{3}} n32 , 即 pow(n, 0.67) 。

P1903 [国家集训队] 数颜色 / 维护队列

因为 t t t 的大小会不同, 我们还需要取消操作, 其实就是记一下进行每次修改时, 那个格子是什么颜色, 取消修改就是给它颜色还原回去嘛。

当时 color 那个数组大小我开错了, 找了半天, 还是让 deepseek 帮我看的。 我当时是把那个数组的大小开成了查询次数的。

struct node{int l, r, t, idx;
};
node Q[133333];
int cnt[int(1e6)+1];void ChatGptDeepSeek() // Date: 2025-04-26
{                      // Time: 01:16:57 int n, m;cin >> n >> m;vi c(n + 1), pos(n + 1);int siz = pow(n, 0.67) + 1;for(int i = 1; i <= n; i++)cin >> c[i], pos[i] = i / siz;vector<pii> upd;int tot = 0;for(int i = 0, cnt = 0; i < m; i++){char op;int x, y;cin >> op >> x >> y;if(op == 'R') upd.push_back({x, y}), cnt++;else Q[tot++] = {x, y, cnt, tot-1};}if(!tot) return;sort(Q, Q + tot, [&](node x, node y){if(pos[x.l] == pos[y.l]){if(pos[x.r] == pos[y.r]) return x.t < y.t;return pos[x.r] < pos[y.r];}return pos[x.l] < pos[y.l];});vi ans(tot), color(sz(upd));int L = 1, R = 0, T = 0, res = 0;auto add = [&](int i){cnt[c[i]]++;if(cnt[c[i]] == 1) res++;};auto sub = [&](int i){cnt[c[i]]--;if(cnt[c[i]] == 0) res--;};auto change = [&](int t){auto [P, C] = upd[t];color[t] = c[P];if(P >= L && P <= R){cnt[c[P]]--;if(cnt[c[P]] == 0) res--;cnt[C]++;if(cnt[C] == 1) res++;}c[P] = C;};auto recover = [&](int t){auto [P, C] = upd[t];c[P] = color[t];if(P >= L && P <= R){cnt[C]--;if(cnt[C] == 0) res--;cnt[c[P]]++;if(cnt[c[P]] == 1) res++;}};for(int i = 0; i < tot; i++){auto [l, r, t, idx] = Q[i];while(L > l) add(--L);while(R < r) add(++R);while(L < l) sub(L++);while(R > r) sub(R--);while(T < t) change(T++);while(T > t) recover(--T);ans[idx] = res;}for(int i = 0; i < tot; i++)cout << ans[i] << '\n';
}
http://www.xdnf.cn/news/2869.html

相关文章:

  • Spring-全面详解(学习总结)
  • 具身智能机器人的应用场景及最新进展
  • Java写项目前的准备工作指南(技术栈选择 环境搭建和工具配置 项目结构设计与模块划分)
  • springboot 视频分段加载在线播放
  • 【含文档+PPT+源码】基于微信小程序的旅游论坛系统的设计与实现
  • Linux中的计划任务
  • Python----卷积神经网络(卷积为什么能识别图像)
  • 顶会idea:Mamba+CNN暴力涨点新突破!
  • vue复习91~135
  • Blender插件 幽灵拖尾特效动画 Ghosts V1.0
  • 重测序关系矩阵构建方式汇总
  • 什么是SCADA系统?
  • Redis ⑦-set | Zset
  • [OS] POSIX C库介绍
  • pgrep和pkill命令详解
  • Dockerfile讲解与示例汇总
  • C#进阶学习(十六)C#中的迭代器
  • VASP 教程:VASP 结合 phonopy 计算硅的声子谱
  • Electron 入门指南
  • [minilibc] 库文件的调用放置
  • 案例篇:如何用tcpdump和Wireshark识别潜在威胁
  • 大学之大:韩国科学技术研究院2025.4.28
  • Python依据卫星TLE轨道根数,计算可见时间窗口
  • Web 基础与Nginx访问统计
  • SECS-I vs HSMS-SS vs HSMS-GS 通信控制对比明细表
  • TypeScript 实用类型深度解析:Partial、Pick、Record 的妙用
  • SQL常用数据清洗语句
  • Python爬虫学习路径与实战指南 02
  • 苍穹外卖10
  • React学习