题目链接: zoto
大致题意
给定一个长度为 n n n的序列, 有 m m m次询问, 每次询问给出l a r b
.
表示询问在区间 [ l , r ] [l, r] [l,r]内, 值域 [ a , b ] [a, b] [a,b]之间有多少个不同数值.
和原题叙述略有不同, 但是本质是这样.
解题思路
莫队 + 值域分块
我们考虑要得到区间 [ l , r ] [l, r] [l,r]的信息, 我们可以通过莫队来实现 O ( n ) O(\sqrt{n}) O(n)的修改, O ( 1 ) O(1) O(1)的查询.
本题需要统计某个值域范围内有多少个不同的数值. 如果我们暴力一点, 用树状数组维护, 可以做到 O ( n l o g n ) O(\sqrt{n}logn) O(nlogn)的修改, O ( 1 ) O(1) O(1)的查询.
但是这样做的话, 修改的复杂度太高了, 于是我们考虑把修改的复杂度摊在查询上.
我们可以通过对值域进行分块的方式, 去记录每个值域块内的元素种类, 这样可以做到 O ( n ) O(\sqrt{n}) O(n)的修改, O ( n ) O(\sqrt{n}) O(n)的查询.
值域分块这个东西还是比较好理解的, 如果不会的话, 学一下分块的基本思想, 然后对照代码看看就明白了.
➡️同类题目推荐⬅️
AC代码
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 1E5 + 10, B = 320;
int w[N];
struct mo {
int l, r, a, b, id;
bool operator< (const mo& t) const {
if (l / B != t.l / B) return l < t.l;
return l / B & 1 ? r < t.r : r > t.r;
}
}; vector<mo> area;
int res[N];
int cou[N], bcou[B];
void add(int c) {
if (++cou[c] == 1) bcou[c / B]++; }
void sub(int c) {
if (!--cou[c]) bcou[c / B]--; }
int ask(int a, int b) {
int res = 0;
if (a / B == b / B) {
while (a <= b) res += cou[a++] != 0;
return res;
}
int la = a / B, lb = b / B;
while (a / B == la) res += cou[a++] != 0;
while (b / B == lb) res += cou[b--] != 0;
for (int i = a / B; i <= b / B; ++i) res += bcou[i];
return res;
}
int main()
{
int T; cin >> T;
while (T--) {
area.clear();
int n, m; scanf("%d %d", &n, &m);
rep(i, n) scanf("%d", &w[i]);
rep(i, m) {
int l, r, a, b; scanf("%d %d %d %d", &l, &a, &r, &b);
area.push_back({
l, r, a, b, i });
}
sort(area.begin(), area.end());
int L = 1, R = 0;
for (auto& op : area) {
int l = op.l, r = op.r, a = op.a, b = op.b, id = op.id;
while (l < L) add(w[--L]);
while (r > R) add(w[++R]);
while (L < l) sub(w[L++]);
while (R > r) sub(w[R--]);
res[id] = ask(a, b);
}
while (L <= R) sub(w[L++]); // 清空信息
rep(i, m) printf("%d\n", res[i]);
}
return 0;
}