按右端点离线处理询问,用树状数组只保留每种颜色在当前前缀中的最后出现位置。
OJ: luogu
题目 ID: P1972
难度:普及+/提高
标签:离线树状数组数据结构
日期: 2026-06-22 23:16
题意
给定一个颜色序列,多次询问区间
思路
朴素做法是每次询问直接扫描区间,用桶统计不同颜色。
先看一个可以直接验证想法的朴素解:
cpp
#include <bits/stdc++.h>
using namespace std;
// brute.cpp:每个询问直接用桶统计不同颜色,只适合小数据。
const int MAXN = 505;
const int MAXC = 1005;
int n, m;
int a[MAXN];
int seen[MAXC];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
cin >> m;
for (int i = 1; i <= m; i++) {
int l, r;
cin >> l >> r;
int answer = 0;
for (int c = 0; c < MAXC; c++) {
seen[c] = 0;
}
for (int j = l; j <= r; j++) {
if (!seen[a[j]]) {
seen[a[j]] = 1;
answer++;
}
}
cout << answer << '\n';
}
return 0;
}朴素做法在大数据下会超时。考虑把询问离线,按照右端点
当我们已经处理到前缀
- 如果某种颜色在
中出现,它在前缀 的最后出现位置一定在 内; - 如果它最后出现位置小于
,说明它没有出现在 中。
所以答案就是当前树状数组中
每加入一个新位置
代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1000005;
struct Query {
int l, r, id;
};
int n, m;
int a[MAXN];
int last_pos[MAXN]; // last_pos[color] 表示这种颜色最近一次出现的位置。
int bit_tree[MAXN]; // 树状数组维护每种颜色在当前前缀中的最后一次出现位置。
int answer[MAXN];
Query queries[MAXN];
bool cmp_query(const Query &x, const Query &y) {
return x.r < y.r;
}
void add(int pos, int value) {
while (pos <= n) {
bit_tree[pos] += value;
pos += pos & -pos;
}
}
int prefix_sum(int pos) {
int sum = 0;
while (pos > 0) {
sum += bit_tree[pos];
pos -= pos & -pos;
}
return sum;
}
int range_sum(int l, int r) {
return prefix_sum(r) - prefix_sum(l - 1);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
cin >> m;
for (int i = 1; i <= m; i++) {
cin >> queries[i].l >> queries[i].r;
queries[i].id = i;
}
sort(queries + 1, queries + m + 1, cmp_query);
int now_r = 0;
for (int i = 1; i <= m; i++) {
while (now_r < queries[i].r) {
now_r++;
// 每种颜色只在它最新出现的位置贡献 1。
if (last_pos[a[now_r]] != 0) {
add(last_pos[a[now_r]], -1);
}
add(now_r, 1);
last_pos[a[now_r]] = now_r;
}
answer[queries[i].id] = range_sum(queries[i].l, queries[i].r);
}
for (int i = 1; i <= m; i++) {
cout << answer[i] << '\n';
}
return 0;
}复杂度
时间复杂度
空间复杂度
总结
这题的关键是“只保留最后一次出现位置”。离线按右端点推进后,区间不同颜色数就变成了树状数组上的区间求和。