莫队从入门到人门

news/2024/12/25 21:10:46/文章来源:https://www.cnblogs.com/biyimouse/p/18631414

普通莫队

详介(P2709 小B的询问)

普通莫队处理问题的前提是问题可以离线多次区间查询\(O(n\sqrt m)\) 能过。

我们以 P2709 小B的询问 为例,假设当前区间为 \([l,r]\),答案为 \(ans\),那么 \(r\) 右移一位时,新加入一个数 \(x\),我们只要把 \(ans\) 加上 \(x\) 的贡献即可。贡献只需要维护一下当前区间内 \(x\) 的出现次数 \(cnt[x]\) 即可。

所以我们可以初始 \(l = 1, r = 0\),每次向一个询问的区间移动 \(l\)\(r\),然后维护当前区间的答案。

我们发现,如果两个查询区间的差距很大,我们每次就要跑很远才能到答案,所以我们要将所有的询问离线下来,进行分块排序

具体地,我们将所有的询问进行分块,将所有的询问按照左端点所在块为第一关键字,右端点升序进行排序,这样排完序后我们发现在一个块中 \(r\) 的移动为 \(O(n)\),设当前块长为 \(B\),那么总共就是 \(O(\frac{n^2}{B})\),而左端点在每两个询问之间移动次数不超过 \(O(B)\),所以总共为\(O(mB)\),两者相加就是 \(O(mB + \frac{n^2}{B})\)。发现取 \(B\)\(\frac{n}{\sqrt m}\) 时复杂度最优为 \(O(n\sqrt m)\)

这是 \(P2709\) 的代码。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 50010;
int n, m, k, w[N], B, cnt[N], ans[N];
LL res;struct node {int l, r, id;bool operator < (const node &t) const {if (l / B == t.l / B) return r < t.r;else return l / B < t.l / B;}
} q[N];void add(int x) {res -= cnt[x] * cnt[x];cnt[x] ++;res += cnt[x] * cnt[x];
}void del(int x) {res -= cnt[x] * cnt[x];cnt[x] --;res += cnt[x] * cnt[x];
}int main() {scanf("%d%d%d", &n, &m, &k); B = sqrt(n);for (int i = 1; i <= n; i ++) scanf("%d", &w[i]);for (int i = 1; i <= m; i ++) { scanf("%d%d", &q[i].l, &q[i].r); q[i].id = i; }sort(q + 1, q + 1 + m);int l = 1, r = 0;for (int i = 1; i <= m; i ++) {while (l > q[i].l) add(w[-- l]);while (r < q[i].r) add(w[++ r]);while (l < q[i].l) del(w[l ++]);while (r > q[i].r) del(w[r --]);ans[q[i].id] = res;}for (int i = 1; i <= m; i ++) printf("%d\n", ans[i]);return 0;
}

P1494 小 Z 的袜子

这同样是一道莫队题,我们发现答案应该是:

\[\frac{\sum{col}\ cnt_{col} \times (cnt_{col} - 1)}{(R - L + 1) \times (R-L)} \]

其中 \(col\)\([L,R]\) 中出现的所有颜色,\(cnt_{col}\) 是该颜色出现的次数,我们修改一下莫队的 \(add\)\(del\),维护分子即可。

记得约分和特判。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 50010;
int n, m, k, w[N], B, cnt[N], ans[N], ans2[N];
LL res;struct node {int l, r, id;bool operator < (const node &t) const {if (l / B == t.l / B) return r < t.r;else return l / B < t.l / B;}
} q[N];void add(int x) {res -= cnt[x] * (cnt[x] - 1) / 2;cnt[x] ++;res += cnt[x] * (cnt[x] - 1) / 2;
}void del(int x) {res -= cnt[x] * (cnt[x] - 1) / 2;cnt[x] --;res += cnt[x] * (cnt[x] - 1) / 2;
}LL gcd(LL a, LL b) {return b ? gcd(b, a % b) : a;
}int main() {scanf("%d%d", &n, &m); B = sqrt(n);for (int i = 1; i <= n; i ++) scanf("%d", &w[i]);for (int i = 1; i <= m; i ++) { scanf("%d%d", &q[i].l, &q[i].r); q[i].id = i; }sort(q + 1, q + 1 + m);int l = 1, r = 0;for (int i = 1; i <= m; i ++) {while (l > q[i].l) add(w[-- l]);while (r < q[i].r) add(w[++ r]);while (l < q[i].l) del(w[l ++]);while (r > q[i].r) del(w[r --]);ans[q[i].id] = res; ans2[q[i].id] = (LL)(r - l + 1) * (r - l) / 2;}for (int i = 1; i <= m; i ++) {if (ans[i] == 0) ans2[i] = 1;LL g = gcd(ans[i], ans2[i]);printf("%lld/%lld\n", ans[i] / g, ans2[i] / g);}return 0;
}

可能还有例题,后面再做。

带修莫队

详介

带修莫队可以理解为一种广义高维莫队,顾名思义,可以支持修改。做法也十分简单,我们只需要给莫队增加一维时间即可。时间指的是在这个查询操作前一共有多少个修改操作。由于加了一维时间,所以我们可以按照第一维左端点所在块,第二维右端点所在块,第三维时间,分别从小到大排序。

struct query {int l, r, id, t;bool operator < (const query &T) const {if (l / B != T.l / B) return l / B < T.l / B;else if (r / B != T.r / B) return r / B < T.r / B;else return t < T.t;}
} q[N];

同时我们还需要存一下所有的修改操作。

struct operation {int p, x;
} op[N];

莫队如何维护时间维呢?我们发现如果当前的修改的 \(p\) 属于当前莫队区间 \([l,r]\),那么就对第 \(p\) 个位置原本的数 \(y\)\(del(y)\),然后再对于修改的 \(x\)\(add(x)\),最后由于每一个修改操作的使用和清除一定是成对出现的,所以我们直接将 \(x\)\(y\) 进行交换即可。

void upd(int last, int l, int r) { // last 是当前操作编号if (op[last].p >= l && op[last].p <= r) {add(op[last].x);del(a[op[last].p]);}swap(op[last].x, a[op[last].p]);
}

我们再考虑块长应该取多少,我们直接考虑 \(k\) 维莫队,其中前 \(k - 1\) 维按所在块升序,第 \(k\) 维升序,那么一共会出现 \((\frac{n}{B})^{k-1}\) 种块的情况,每种情况下右端点最多移动 \(O(n)\),每切换一次询问左端点最多移动 \(O((k-1)B)\),所以总的时间复杂度就应该是 \(O((\frac{n}{B})^{k-1} \times n + (k-1)Bm)\),根据基本不等式当 \(B = \sqrt[k]{\frac{n^k}{(k-1)m}}\) 时取到最优,如果将 \((k-1)\) 视为常数,并且 \(n、m\) 同阶,那么 \(B\) 就可以取到 \(n^{\frac{k-1}{k}}\)

回到带修莫队,就相当于一个三维的莫队,那么 \(B\) 就取 \(n^{\frac{2}{3}}\),带回计算可得到时间就是 \(O(n^{\frac{5}{3}} + 2 \times n^{\frac{1}{3}}m)\),可以看做 \(O(n^{\frac{5}{3}})\)

例题 P1903 数颜色

贴一份带修莫队的代码。

#include <bits/stdc++.h>
using namespace std;
const int N = 150010, M = 1000010;
int n, m, a[N], B;
int cnt[M], res, qcnt, opcnt;
int ans[N];struct query {int l, r, id, t;bool operator < (const query &T) const {if (l / B != T.l / B) return l / B < T.l / B;else if (r / B != T.r / B) return r / B < T.r / B;else return t < T.t;}
} q[N];struct operation {int p, x;
} op[N];void add(int x) {if (!cnt[x]) res ++;cnt[x] ++; 
}void del(int x) {cnt[x] --;if (!cnt[x]) res --;
}void upd(int last, int l, int r) {if (op[last].p >= l && op[last].p <= r) {add(op[last].x);del(a[op[last].p]);}swap(op[last].x, a[op[last].p]);
}int main() {scanf("%d%d", &n, &m);for (int i = 1; i <= n; i ++) scanf("%d", &a[i]);B = pow(n, 2.0 / 3.0);for (int i = 1; i <= m; i ++) {char t[2]; int l, r;scanf("%s%d%d", t, &l, &r);if (*t == 'Q') q[++ qcnt] = {l, r, qcnt, opcnt};else op[++ opcnt] = {l, r};}sort(q + 1, q + 1 + qcnt);int l = 1, r = 0, last = 0;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 (last < q[i].t) upd(++ last, l, r);while (last > q[i].t) upd(last --, l, r);ans[q[i].id] = res;}for (int i = 1; i <= qcnt; i ++) printf("%d\n", ans[i]);return 0;
} 

树上莫队

详介 (SP10707 COT2 - Count on a tree II)

顾名思义,是在“树上”的莫队。我们来看一个例子(就是本段标题的题目),要求树上路径上的不同颜色数。

我们发现在树上进行莫队十分的不方便,我们考虑将其转化成一维的序列。我们可以使用欧拉序。

对于这幅图,它的欧拉序(以 \(1\) 为更)便是 \([1,3,5,5,6,6,7,7,3,2,2,4,8,8,4,1]\)。我们考虑一个点 \(u\)\(v\) 的路径在欧拉序中的表示,我们记 \(st[x]\)\(ed[x]\) 分别表示一个点的第一次和最后一次出现的位置,那么我们可以分类讨论(假定 \(st[u] < st[v]\)):

  1. \(u\)\(v\) 的祖宗,如 \(1 -> 6\),我们可以截取欧拉序中的 \([1,3,5,5,6]\),即 \(st[1] \sim st[6]\) 之间的所有。我们发现 \(5\) 出现了两次,相当于进去了有出来,可以删掉,剩下的 \([1,3,6]\) 便是我们的路径。
  2. \(u\) 不是 \(v\) 的祖宗,如 \(3 -> 8\),那么我们截取 \(ed[3] \sim st[8]\) 之间的所有,再删去出现两次的 \(2\),剩下 \([3,4,8]\) 我们发现路径中还少了 \(3\)\(8\)\(lca\),加上即可。

综上,我们可以将 \(u -> v\) 的路径用欧拉序巧妙的表示。这样子树上的询问就变成区间,可以使用莫队。由于我们使用的是普通莫队,所以块长为 \(\frac{n}{\sqrt m}\)。同时,为了维护一个点的出现次数,我们可以使用异或,\(0\) 表示该点第一次出现,可加入,\(1\) 表示第二次,要删除。

注意到颜色 \(\leq 2e9\),需要离散化。

#include <bits/stdc++.h>
using namespace std;
const int N = 200010, M = 100010;
int n, m, w[N];
int h[N], e[N], ne[N], idx;
int fa[N][20], depth[N], in[N];
int st[N], ed[N], q[N], len;
int vis[N], cnt[N], res, ans[M];
int c[N];
vector<int> v;int find(int x) {return lower_bound(v.begin(), v.end(), x) - v.begin();
}void add_edge(int a, int b) {e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}// lca 的预处理
void bfs(int root) {queue<int> q; q.push(root);memset(depth, 0x3f, sizeof depth);depth[0] = 0, depth[root] = 1;while (q.size()) {int t = q.front(); q.pop();for (int i = h[t]; ~i; i = ne[i]) {int j = e[i];if (depth[j] > depth[t] + 1) {depth[j] = depth[t] + 1;fa[j][0] = t;q.push(j);for (int k = 1; k < 20; k ++) fa[j][k] = fa[fa[j][k - 1]][k - 1];}}}
}// 预处理欧拉序
void dfs(int u, int fa) {q[++ len] = u, st[u] = len;for (int i = h[u]; ~i; i = ne[i]) {int j = e[i];if (j == fa) continue;dfs(j, u);}q[++ len] = u, ed[u] = len;
}// 倍增求 lca
int lca(int a, int b) {if (depth[a] < depth[b]) swap(a, b);for (int k = 19; k >= 0; k --)if (depth[fa[a][k]] >= depth[b])a = fa[a][k];if (a == b) return a;for (int k = 19; k >= 0; k --)if (fa[a][k] != fa[b][k]) {a = fa[a][k];b = fa[b][k];}  return fa[a][0];
}int B;
struct query {int l, r, id, lca;bool operator < (const query &t) const {return l / B < t.l / B || l / B == t.l / B && r > t.r;}
} que[N];void add(int x) {if (!cnt[x]) res ++;cnt[x] ++;
}void del(int x) {cnt[x] --;if (!cnt[x]) res --;
}void upd(int x) {if (!vis[x]) add(c[x]);else del(c[x]);vis[x] ^= 1;
}int main() {scanf("%d%d", &n, &m);memset(h, -1, sizeof h);B = n / sqrt(m);for (int i = 1; i <= n; i ++) {scanf("%d", &w[i]);v.push_back(w[i]);}for (int i = 1; i < n; i ++) {int a, b; scanf("%d%d", &a, &b);add_edge(a, b); add_edge(b, a);}sort(v.begin(), v.end());v.erase(unique(v.begin(), v.end()), v.end());for (int i = 1; i <= n; i ++) c[i] = find(w[i]);bfs(1); dfs(1, -1);for (int i = 1; i <= m; i ++) {int u, v; scanf("%d%d", &u, &v);int s = lca(u, v);if (s == u || s == v) {if (st[v] < st[u]) swap(u, v);que[i] = {st[u], st[v], i};} else {if (ed[v] < st[u]) swap(u, v);que[i] = {ed[u], st[v], i, s};}}sort(que + 1, que + 1 + m);int l = 1, r = 0;for (int i = 1; i <= m; i ++) {while (l > que[i].l) upd(q[-- l]);while (r < que[i].r) upd(q[++ r]);while (l < que[i].l) upd(q[l ++]);while (r > que[i].r) upd(q[r --]);if (que[i].lca) upd(que[i].lca);ans[que[i].id] = res;if (que[i].lca) upd(que[i].lca);}for (int i = 1; i <= m; i ++) printf("%d\n", ans[i]);return 0;
}

P4074 糖果公园

注意到题目求的其实就是 \(u->v\) 路径上的 \(\sum_{col\in c_{u->v}}\ v_{col}\times \sum{w_{col}}\),并且需要支持修改,我们使用树上莫队和带修莫队的融合版即可。

代码有点复杂。注意 \(modify\) 中的 \(x\)\(p\) 位置在进行本次修改前应该是什么值,而 \(t\) 存的是本次修改后的值。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 200010;
int n, m, Q;
int v[N], w[N], c[N], last[N];
int h[N], e[N], ne[N], idx;
int fa[N][22], depth[N];
int q[N], st[N], ed[N], len;
LL cnt[N], dis[N], ans[N], cntw[N];
LL res;void add(int a, int b) {e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}   void bfs(int root) {queue<int> q; q.push(root);memset(depth, 0x3f, sizeof depth);depth[0] = 0, depth[root] = 1;while (q.size()) {int t = q.front(); q.pop();for (int i = h[t]; ~i; i = ne[i]) {int j = e[i];if (depth[j] > depth[t] + 1) {depth[j] = depth[t] + 1;fa[j][0] = t;q.push(j);for (int k = 1; k <= 20; k ++) fa[j][k] = fa[fa[j][k - 1]][k - 1];}}}
}void dfs(int u, int fa) {q[++ len] = u, st[u] = len;for (int i = h[u]; ~i; i = ne[i]) {int j = e[i];if (j == fa) continue;dfs(j, u);}q[++ len] = u, ed[u] = len;
}int lca(int a, int b) {if (depth[a] < depth[b]) swap(a, b);for (int k = 20; k >= 0; k --)if (depth[fa[a][k]] >= depth[b])a = fa[a][k];if (a == b) return a;for (int k = 20; k >= 0; k --)if (fa[a][k] != fa[b][k]) {a = fa[a][k];b = fa[b][k];}  return fa[a][0];
}int B, qcnt, rcnt;
struct Query {int l, r, id, t, lca;bool operator < (const Query &T) const {if (l / B != T.l / B) return l / B > T.l / B;else if (r / B != T.r / B) return r / B > T.r / B;else return t > T.t;}
} query[N];struct Modify {int p, x, t;
} modify[N];// 写法和上一题不一样,感觉 OIWiki 上的更简洁一点
void add(int x) {if (dis[x]) res -= (LL)v[c[x]] * w[cnt[c[x]]--];else res += (LL)v[c[x]] * w[++ cnt[c[x]]];dis[x] ^= 1;
}void upd(int p, int x) {if (dis[p]) {add(p); c[p] = x; add(p);} else c[p] = x;
} int main() {scanf("%d%d%d", &n, &m, &Q);memset(h, -1, sizeof h);for (int i = 1; i <= m; i ++) scanf("%d", &v[i]);for (int i = 1; i <= n; i ++) scanf("%d", &w[i]);for (int i = 1; i < n; i ++) {int a, b; scanf("%d%d", &a, &b);add(a, b); add(b, a);}for (int i = 1; i <= n; i ++) { scanf("%d", &c[i]); last[i] = c[i]; }bfs(1); dfs(1, -1);B = pow(len, 2.0 / 3.0);while (Q --) {int op, x, y; scanf("%d%d%d", &op, &x, &y);if (op == 0) {// last[x] 表示位置 x 修改前的值modify[++ rcnt] = {x, last[x]};last[x] = modify[rcnt].t = y;} else {int s = lca(x, y);if (s == x || s == y) {if (st[y] < st[x]) swap(x, y);query[++ qcnt] = {st[x], st[y], qcnt, rcnt, 0};} else {if (st[y] < st[x]) swap(x, y);query[++ qcnt] = {ed[x], st[y], qcnt, rcnt, s};}}}sort(query + 1, query + 1 + qcnt);int l = 1, r = 0, last = 0;for (int i = 1; i <= qcnt; i ++) {while (l > query[i].l) add(q[-- l]);    while (r < query[i].r) add(q[++ r]);while (l < query[i].l) add(q[l ++]);while (r > query[i].r) add(q[r --]);    // 这里注意,拓展时用的是修改后的值,恢复时用的是修改前的值while (last < query[i].t) last ++, upd(modify[last].p, modify[last].t);while (last > query[i].t) upd(modify[last].p, modify[last].x), last --;    if (query[i].lca) add(query[i].lca);ans[query[i].id] = res;if (query[i].lca) add(query[i].lca);}for (int i = 1; i <= qcnt; i ++) printf("%lld\n", ans[i]);return 0;
}

回滚莫队

在写

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/858839.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

nacos安装注意事项

一年多没玩了,都快忘了,最新版本已经2.3.x了 3.0也快问世了 Linux/Unix/Mac 单机启动命令: sh startup.sh -m standalone Windows startup.cmd -m standalone如果直接未启动就是集群模式,但是要注意nacos.properties里面配置集群信息本文来自博客园,作者:余生请多指教ANT…

PWN系列-2.27版本利用setcontext实现orw

PWN系列-2.27版本利用setcontext实现orw 知识 开启沙箱之后,我们就只能用orw的方式来得到flag。 这篇博客主要讲通过劫持__free_hook或者__malloc_hook利用setcontext在libc或者heap上执行rop或者shellcode。 在free堆块的时候,rdi会指向堆块,在检测到__free_hook有值的情况…

shell语法保姆级教程

Shell脚本 建立一个sh脚本 touch 1.sh (新建脚本文件)vi 1.sh(编写文件内容)按 i 可以写入内容,按esc :wq退出并保存解释 1、创建脚本文件 2、脚本文件中第一行为指定脚本编译器:# !/bin/bash 最终调用的都是dash执行shell脚本命令: 1、./1.sh难道我们必须要修改权限才能执…

从0开始学uniapp——认识HBuilderX

为什么使用uniapp:可以多端运行,写好了这一套可以用在h5,安卓程序,小程序多端,很方便。1.百度搜HBuilderX,使用该编译器学习uniapp 2.新建一个默认项目 pages——用于存放页面,这里都是.vue后缀的页面, pages.json——用于存放路由pages数组里按例子添加即可,HBuilder…

Java中SPI机制原理解析

本文介绍了Java中SPI机制实现的大概原理以及SPI机制在常见的框架如JDBC的Driver加载,SLF4J日志门面实现中的使用。使用SPI机制前后的代码变化加载MySQL对JDBC的Driver接口实现 在未使用SPI机制之前,使用JDBC操作数据库的时候,一般会写如下的代码:// 通过这行代码手动加载My…

Transformers 框架 Pipeline 任务详解(六):填充蒙版(fill-mask)

本文介绍了Hugging Face Transformers框架中的fill-mask任务,涵盖其作用、应用场景如机器翻译和文本补全,以及配置方法。通过Python代码示例展示了如何使用预训练模型自动下载或本地加载来创建Pipeline并执行填空任务。此外,还提供了利用Gradio构建WebUI界面的指南,使用户能…

阿里发布多模态推理模型 QVQ-72B,视觉、语言能力双提升;OpenAI 正在研发人形机器人丨 RTE 开发者日报

开发者朋友们大家好:这里是 「RTE 开发者日报」 ,每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE(Real-Time Engagement) 领域内「有话题的 新闻 」、「有态度的 观点 」、「有意思的 数据 」、「有思考的 文章 」、「有看点的 会议 」,但内容仅代表编辑…

python多进程,通过内存共享来通信,使用进程锁来防止数据问题

代码:import multiprocessing import time 使用锁和multiprocessing.Value,multiprocessing.Array,multiprocessing.Manager().listdef worker1(shared_number1, lock):for _ in range(10):with lock:shared_number1.value += 1def worker2(shared_array1, lock):for i in…

Jenkins入门使用

Jenkins入门使用 1先安装jdk才能运行jenkins yum install -y java-1.8.0-openjdk.x86_642 安装jenkins,运行,进行端口绑定,启动jenkins docker search jenkins docker pull jenkins/jenkins docker run -d -u root -p 8080:8080 -p 50000:50000 -v /var/jenkins_home:/var/j…

Java 泛型详细解析

本文将带你详细解析 Java 泛型,了解泛型的原理、常见的使用方法以及泛型的局限性,让你对泛型有更深入的了解。泛型的定义 泛型类的定义 下面定义了一个泛型类 Pair,它有一个泛型参数 T。 public class Pair<T> {private T start;private T end; }实际使用的时候就可以…

javafx-请求篇

OkHttpClient 基本使用步骤如下构建客户端对象OkHttpClient 构建请求Request 生成Call对象 Call发起请求(同步/异步)import java.io.IOException; import okhttp3.Call; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Req…

javafx-一个小demo

懒得讲了,直接看代码吧 pox.xml<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://m…