「笔记」莫队算法

写在前面

呃呃呃呃之前学莫队的时候怎么没写博客。

稍微回想了一下,学莫队的时候好像是高一下疫情在家期间,当时天天摆,怪不得没写。

唉,唉,唉!

简单记录下板子。

莫队的时间复杂度证明

抄自:https://www.cnblogs.com/luckyblock/p/13629547.html

设序列长度为 n,询问次数为 m,假设可以 O(1) 地处理端点的移动和回答询问。

考虑莫队算法中对询问的排序过程:先按照左端点的块编号升序排序,左端点在同一块中的按右端点升序排序。设块大小为 T,排序后将询问分为了 nT 块,每块内右端点单调递增。

考虑每一块的右端点单调递增,移动的复杂度为 O(n)。右端点的垮块移动量上界为 O(n),则右端点移动的总复杂度为 O(n2T)。对于块内的每一次询问,左端点移动量 T。垮块移动左端点的改变量为 T,则左端点移动的总复杂度为 O(mT)

算法的总复杂度为 O(n2T+mT+m),由均值不等式,n2T+mT2n2m,当且仅当 n2T=mT 时,复杂度最低。解得最优块大小为 nm,算法总复杂度为 O(nm+m)

如果简单将块大小设为 n,得算法总复杂度为 O(nn+mn+m)。与上面得到的最优复杂度作差,得:

nn+mnnm=n(n+mnm)

考虑括号内的三项,由均值不等式,有:

n+mnm2mm=m>0

得块大小设为 n 劣于块大小为 nm


呃呃其实上面的分析只是考虑了运行效率的上界,并不能代表实际运行效率。

为了省事儿块大小一般还是取 n

要是卡不过去那就多调几遍吧!

要是实在卡不过去那就别惦记你那 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

单点修改,询问区间内权值种类数。

块长取 n23,不会证。

//
/*
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

求欧拉序,转成序列问题。

注意判断 v 是否在 u 的子树中,两种情况询问区间不同,如果不在还需要额外统计 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;
}

写在最后

呃呃。

posted @   Luckyblock  阅读(22)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示