前言
一道树状数组题。
题目分析
简化题意
题目要求寻找一段数组中某些区间内的不同数的个数。
思路
考虑暴力,如果对于每个区间都遍历一遍并计数,时间复杂度将达到 O ( n 2 ) O(n^{2}) O(n2) 之高,考虑优化。
类似于单调队列,对于每个区间来说,相同数字中我们可以认为只有最后一个出现的数字才会起到作用(即会代表与它相等的数字被统计)。所以我们可以考虑离线,按照区间的右端点 r r r 顺序排序。之后遍历区间,记录从数组左端到遍历到的位置 i i i 中每种数字的最后一个出现的位置。对于 r = i r = i r=i 的询问,我们可以计算其左端点到 i i i 中出现不同数的个数。
对于统计每种数字的最后一个出现的位置,我们可以使用树状数组加桶的办法来记录。时间复杂度 O ( n log n ) O(n \log n) O(nlogn)。
注意使用较快的读入方式。
#include<bits/stdc++.h>
using namespace std;
const int NR = 1e6;
int a[NR + 10];
struct Node{ //离线记录区间
int l, r, id;
}b[NR + 10];
int c[NR + 10];
bool cmp(Node x, Node y){ //按照 r 排序
return x.r < y.r;
}
//树状数组
int lowbit(int x){
return x & -x;
}
int n;
void add(int u, int num){
for(int i = u;i <= n;i += lowbit(i)){
c[i] += num;
}
return ;
}
int solve(int r){
int ret = 0;
for(int i = r;i >= 1;i -= lowbit(i)){
ret += c[i];
}
return ret;
}
int ans[NR + 10]; //离线
map<int, int> mp; //桶子
int main(){
scanf("%d", &n);
for(int i = 1;i <= n;i++){
scanf("%d", &a[i]);
}
int m;
scanf("%d", &m);
for(int i = 1;i <= m;i++){
scanf("%d%d", &b[i].l, &b[i].r);
b[i].id = i;
}
sort(b + 1, b + 1 + m, cmp); //排序
int pos = 1;
//遍历
for(int i = 1;i <= n;i++){
if(mp[a[i]]){
add(mp[a[i]], -1);
}
mp[a[i]] = i;
add(i, 1);
while(pos <= m && b[pos].r <= i){ //计算所有右端点被包括的区间
ans[b[pos].id] = solve(b[pos].r) - solve(b[pos].l - 1); //记录答案
pos++;
}
}
//输出
for(int i = 1;i <= m;i++){
printf("%d\n", ans[i]);
}
return 0;
}