原题链接
题意
给定一个长度为
n
n
n (
n
≤
1
e
5
n \leq 1e5
n≤1e5) 的序列
a
n
a_n
an (
a
i
≤
1
e
5
a_i\leq 1e5
ai≤1e5),
m
m
m (
m
≤
1
e
5
m \leq 1e5
m≤1e5) 次询问,每次询问给定 l r a b
,求区间
[
l
,
r
]
[l,r]
[l,r] 内大于等于
a
a
a 且小于等于
b
b
b 的数的个数,以及满足条件的不同数字的种数。
思路
容易想到莫队,但是对于区间的统计,如果直接莫队的话,需要使用树状数组 / 线段树等数据结构维护区间,时间复杂度会变成 O ( n m log N ) O(n\sqrt m\log N) O(nmlogN),显然无法接受。
朴素的想法,莫队的每次更新是 O ( 1 ) O(1) O(1) 的,查询是 O ( N ) O(N) O(N) 的。因为有 n m n\sqrt m nm 次更新操作和 m m m 次查询操作,显然更新比查询对复杂度的要求更苛刻。如果我们可以 O ( 1 ) O(1) O(1) 地更新、 O ( N ) O(\sqrt N) O(N) 地查询,那么复杂度是可以接受的。
值域分块可以做到这一点。由于是单点更新,显然可以在 O ( 1 ) O(1) O(1) 的时间内完成对单点和块的更新,分块的查询是 O ( N ) O(\sqrt N) O(N) 的,所以总复杂度为 O ( n m + m N ) O(n\sqrt m+m\sqrt N) O(nm+mN)。
代码
#include <bits/stdc++.h>
constexpr int N = 100000;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, m;
std::cin >> n >> m;
std::vector<int> a(n + 1);
for (int i = 1; i <= n; ++i) {
std::cin >> a[i];
}
std::vector<std::array<int, 5>> Q(m);
for (int i = 0; i < m; ++i) {
std::cin >> Q[i][0] >> Q[i][1] >> Q[i][2] >> Q[i][3];
Q[i][4] = i;
}
int len = std::sqrt(n);
std::sort(Q.begin(), Q.end(), [&](auto x, auto y) {
if ((x[0] - 1) / len != (y[0] - 1) / len) return x[0] < y[0];
if ((x[0] - 1) / len & 1) return x[1] > y[1];
return x[1] < y[1];
});
int siz = std::sqrt(N);
int num = (N + siz - 1) / siz;
std::vector<int> st(num + 1), ed(num + 1), id(N + 1);
for (int i = 1; i <= num; ++i) {
st[i] = ed[i - 1] + 1;
ed[i] = std::min(st[i] + siz - 1, N);
for (int j = st[i]; j <= ed[i]; ++j) {
id[j] = i;
}
}
std::vector<std::pair<int, int>> ans(m);
std::vector<int> cnt(N + 1), sum(num + 1), ap(num + 1);
auto add = [&](int x) {
cnt[a[x]]++;
sum[id[a[x]]]++;
if (cnt[a[x]] == 1) ap[id[a[x]]]++;
};
auto del = [&](int x) {
cnt[a[x]]--;
sum[id[a[x]]]--;
if (cnt[a[x]] == 0) ap[id[a[x]]]--;
};
auto query = [&](int fr, int to) -> std::pair<int, int> {
int res1 = 0, res2 = 0;
for (int i = fr; i <= std::min(ed[id[fr]], to); ++i) {
if (cnt[i]) res1 += cnt[i], res2++;
}
if (id[fr] != id[to]) {
for (int i = std::max(fr, st[id[to]]); i <= to; ++i) {
if (cnt[i]) res1 += cnt[i], res2++;
}
}
for (int i = id[fr] + 1; i < id[to]; ++i) {
res1 += sum[i];
res2 += ap[i];
}
return {res1, res2};
};
int l = 1, r = 0;
for (auto [ql, qr, fr, to, id] : Q) {
while (l > ql) add(--l);
while (r < qr) add(++r);
while (l < ql) del(l++);
while (r > qr) del(r--);
ans[id] = query(fr, to);
}
for (auto [x, y] : ans) {
std::cout << x << " " << y << "\n";
}
return 0;
}