原题链接
回滚莫队模板.
排序关键字:(1)左右端点所在块编号是否相同(2)左端点所在块编号(3)右端点
若左右端点所在块编号相同,则暴力计算答案。
对于左端点所在块编号相同的询问,块内暴力处理,块外累加答案。
时间复杂度O(n√n)
代码如下:
#include <cstdio>
#include <cctype>
#include <cmath>
#include <algorithm>
#include <cstring>
using namespace std;
#define ll long long
inline int read(){
int res = 0, pdf = 0; char ch = getchar();
while(!isdigit(ch)) pdf = ch == '-', ch = getchar();
while(isdigit(ch)) res = (res<<3) + (res<<1) + (ch^48), ch = getchar();
return pdf ? -res : res;
}
inline void Print(ll x) {
if (x < 0) x = -x, putchar('-');
if (x < 10) putchar(x + '0');
else {
Print(x / 10);
putchar(x % 10 + '0');
}
}
const int N = 1e5 + 100;
int n, q, block, c[N], br[N], pos[N];
int tmp[N], len;
struct Ask {
int l, r, id; ll ans;
};
Ask a[N];
bool cmp1(Ask x, Ask y) {
if ((pos[x.l] == pos[x.r]) != (pos[y.l] == pos[y.r])) return (pos[x.l] == pos[x.r]) > (pos[y.l] == pos[y.r]);
if (pos[x.l] != pos[y.l]) return pos[x.l] < pos[y.l];
return x.r < y.r;
}
bool cmp2(Ask x, Ask y) {
return x.id < y.id;
}
int ton[N]; ll res;
void Solve() {
for (int i = 1; i <= q; ++i) {
if (pos[a[i].l] == pos[a[i].r]) {
memset(ton, 0, sizeof(ton));
for (int j = a[i].l; j <= a[i].r; ++j) {
++ton[c[j]];
a[i].ans = max(a[i].ans, (ll)ton[c[j]] * (ll)tmp[c[j]]);
}
} else if (pos[a[i].l] != pos[a[i - 1].l]) {
memset(ton, 0, sizeof(ton)); res = 0;
for (int j = br[pos[a[i].l]] + 1; j <= a[i].r; ++j) {
++ton[c[j]];
res = max(res, (ll)ton[c[j]] * (ll)tmp[c[j]]);
}
a[i].ans = res;
for (int j = a[i].l; j <= br[pos[a[i].l]]; ++j) {
++ton[c[j]];
a[i].ans = max(a[i].ans, (ll)ton[c[j]] * (ll)tmp[c[j]]);
}
for (int j = a[i].l; j <= br[pos[a[i].l]]; ++j) {
--ton[c[j]];
}
} else {
for (int j = a[i - 1].r + 1; j <= a[i].r; ++j) {
++ton[c[j]];
res = max(res, (ll)ton[c[j]] * (ll)tmp[c[j]]);
}
a[i].ans = res;
for (int j = a[i].l; j <= br[pos[a[i].l]]; ++j) {
++ton[c[j]];
a[i].ans = max(a[i].ans, (ll)ton[c[j]] * (ll)tmp[c[j]]);
}
for (int j = a[i].l; j <= br[pos[a[i].l]]; ++j) {
--ton[c[j]];
}
}
}
}
int main() {
n = read(); q = read();
for (int i = 1; i <= n; ++i) tmp[i] = c[i] = read();
sort(tmp + 1, tmp + n + 1);
int len = unique(tmp + 1, tmp + n + 1) - (tmp + 1);
for (int i = 1; i <= n; ++i) c[i] = lower_bound(tmp + 1, tmp + len + 1, c[i]) - tmp;
block = sqrt(n);
for (int i = 1; i <= n; ++i) {
pos[i] = i / block + 1;
if (pos[i] != pos[i - 1]) br[pos[i - 1]] = i - 1;
}
br[pos[n]] = n;
for (int i = 1; i <= q; ++i) {
a[i].l = read(); a[i].r = read(); a[i].id = i;
}
sort(a + 1, a + q + 1, cmp1);
Solve();
sort(a + 1, a + q + 1, cmp2);
for (int i = 1; i <= q; ++i) {
Print(a[i].ans); putchar('\n');
}
return 0;
}
补充:
- 若增加操作可以区间转移,删除不能区间转移,则为只使用增加操作的回滚莫队,删除操作交给回滚解决。类似地,有只使用删除操作的回滚莫队.
之前题解写的是啥呀重新整理算法流程:
(1)对询问进行排序:左端点所在块的编号升序为第一关键字,右端点升序为第二关键字.
(2)顺序处理询问:①若左右端点所在块的编号一致,则暴力统计答案.
②初始 l a = 0 la=0 la=0, 若左端点所在块的编号与 l a la la 不一致,则清空桶并使当前 l = 左端点所在块的右端点 + 1 l = 左端点所在块的右端点+1 l=左端点所在块的右端点+1, 并用左端点所在块的编号更新 l a la la.
③转移询问区间.
④回滚使 l = 左端点所在块的右端点 + 1 l = 左端点所在块的右端点+1 l=左端点所在块的右端点+1.
代码如下:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
inline int read() {
int x = 0, f = 0; char ch = getchar();
while (!isdigit(ch)) f = ch == '-', ch = getchar();
while (isdigit(ch)) x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar();
return f ? -x : x;
}
const int N = 100010;
int n, m, col[N], tmp[N], ton[N], ton_[N], len;
int block, pos[N], br[N];
ll res[N], now;
struct Query {
int l, r, id;
bool operator < (const Query &that) const {
if (pos[l] != pos[that.l]) return pos[l] < pos[that.l];
return r < that.r;
}
};
Query q[N];
void add(int c) {
++ton[c];
now = max(now, 1ll * ton[c] * tmp[c]);
}
void del(int c) {
--ton[c];
}
void solve() {
int l = 1, r = 0, la = 0;
for (int i = 1; i <= m; ++i) {
int left = q[i].l, right = q[i].r, id = q[i].id;
if (pos[left] == pos[right]) {
for (int j = left; j <= right; ++j) {
++ton_[col[j]];
res[id] = max(res[id], 1ll * tmp[col[j]] * ton_[col[j]]);
}
for (int j = left; j <= right; ++j) {
--ton_[col[j]];
}
continue;
}
if (pos[left] != la) {
int l_ = br[pos[left]] + 1, r_ = br[pos[left]];
while (l < l_) del(col[l++]);
while (r > r_) del(col[r--]);
// while (r < r_) del(col[r++]);
now = 0;
la = pos[left];
}
while (r < right) add(col[++r]);
ll tp = now;
while (l > left) add(col[--l]);
res[id] = now;
while (l < br[pos[left]] + 1) del(col[l++]);
now = tp;
}
}
int main() {
n = read(); m = read();
for (int i = 1; i <= n; ++i) col[i] = tmp[i] = read();
sort(tmp + 1, tmp + n + 1);
len = unique(tmp + 1, tmp + n + 1) - (tmp + 1);
for (int i = 1; i <= n; ++i) {
col[i] = lower_bound(tmp + 1, tmp + len + 1, col[i]) - tmp;
}
block = sqrt(n);
for (int i = 1; i <= n; ++i) pos[i] = i / block + 1;
for (int i = 1; i <= n; ++i) if (pos[i] != pos[i + 1]) br[pos[i]] = i;
for (int i = 1; i <= m; ++i) {
q[i].l = read(); q[i].r = read(); q[i].id = i;
}
sort(q + 1, q + m + 1);
solve();
for (int i = 1; i <= m; ++i) {
printf("%lld\n", res[i]);
}
}
- 若序列长度为 n n n, 共有 m m m 个询问, 则时间复杂度为 O ( m b + n 2 b ) O(mb+\frac {n^2} {b}) O(mb+bn2), 其中 b b b 为块长,当 b = n m b=\frac{n}{\sqrt{m}} b=mn 时时间复杂度最优,为 O ( n m ) O(n\sqrt{m}) O(nm).
- 题单:P5906 【模板】回滚莫队&不删除莫队 小 trick: 利用
c
l
e
a
r
clear
clear 数组清空桶.
P3709 大爷的字符串题 询问区间众数.
总结:求区间众数的方法
(1) 离线:回滚莫队 O ( n m ) O(n\sqrt{m}) O(nm).
(2) 在线:蒲公英(分块).