题目来源:HH的项链
题目大意:
给定一串项链,每个数字代表一种贝壳,给出多个询问区间,求区间内贝壳的种类数量
解题思路:离线 + 树状数组
1.一个结论
首先我们要知道一个结论,对于许多个右端点相同的询问,总是先考虑在右边的种类对答案的贡献,什么意思?,我们看一个例子
例如:1 1 4 5 1 4(嘿嘿)
对于所有右端点为6的查询,前两个1完全没有意义,1对答案的贡献就可以只考虑第3个1,前两个1完全可以用第3个1来代替
所以我们就可以对所有询问以右端点进行排序,然后维护一个树状数组
2.树状数组维护什么?
树状数组维护的是当前询问区间内种类对答案的贡献,由于之前对查询进行了排序,就可以一边遍历询问(排序后的)一边维护树状数组
我们直接看一个例子:
首先题目中的查询已经排好序了,然后我们遍历询问
<1> 对于询问[1,2] ,我们可以定义一个next来遍历原数组,next的范围小于当前的r,
首先对于1,没有出现过,进行add(1,1),定义数组pos保存这个1的位置,并代表1出现过,对于2同样没有出现过,进行相同的操作,此时树状数组所表示的每个位置上的数字就为:1 1 0 0 0 0
询问1的答案就为sum(2) - sum(1 - 1) = 2 - 0 = 2,我们开一个ans数组把答案存入
操作完成我们令next = r + 1
<2> 对于询问[3,5]
首先对于3和4都没有出现过,我们用上述操作,之后树状数组每个位置的值就为: 1 1 1 1 0 0
然后我们看第5个元素为3,之前出现过,也就是pos数组中记录过上一个3的位置,从上面的结论,就应该只考虑最新出现的种类的贡献,所以我们要进行操作add(pos[3],-1),取消上一个3对答案的贡献,然后再重新add(5,1)记录当前3的贡献并用pos记录位置.
那么此时树状数组每个位置的值就为: 1 1 0 1 1 0
询问2的答案就为sum(5) - sum(3 - 1) = 4 - 2 = 2
询问3也相同
于是这部分的代码就可以这样写:
int next = 1;
for (int i = 1;i <= m; i++) { // m为先想询问个数
for (int j = next;j <= q[i].r; j++) {
if (pos[a[j]]) add(pos[a[j]],-1); // 前面出现过,取消前面的贡献
add(j,1); // 记录贡献
pos[a[j]] = j; // 记录位置
}
next = q[i].r + 1;
// 记录答案
ans[q[i].id] = sum(q[i].r) - sum(q[i].l - 1);
}
3.一些注意点:
1.要记得排序,给的例子是刚好排好序了
2.存答案要按询问序号存,因为按右端点排序之后,序号也排了
4.ACcode
#include <bits/stdc++.h>
using namespace std;
using ll = long long ;
const int N = 1e6 + 50;
struct node {
int l,r,id; // 左右端点和序号
bool operator < (const node a) const { // 结构体的重载
return r < a.r;
}
}q[N]; // 存询问
int n,m;
int a[N]; // 原数组
int tree[N]; // 树状数组
int pos[N]; // 记录元素的位置
int ans[N]; // 记录答案
//------------树状数组板子-----------//
int lowbit(int i) {return i&-i;}
void add(int x,int val) { // 修改操作
while (x <= n) {
tree[x] += val;
x += lowbit(x);
}
}
int sum(int i) { // 求和
int ret = 0;
while (i) {
ret += tree[i];
i -= lowbit(i);
}
return ret;
}
//------------------------------//
int main() {
cin >> n;
for (int i = 1;i <= n; i++) cin >> a[i];
cin >> m;
for (int i = 1;i <= m; i++) {
cin >> q[i].l >> q[i].r;
q[i].id = i; // 记录询问顺序
}
sort(q + 1,q + m + 1) ; // 按右端点排序
int next = 1; // 遍历原数组
for (int i = 1;i <= m; i++) {
for (int j = next;j <= q[i].r; j++) {
if (pos[a[j]]) add(pos[a[j]],-1);
add(j,1);
pos[a[j]] = j;
}
next = q[i].r + 1;
// 记录答案
ans[q[i].id] = sum(q[i].r) - sum(q[i].l - 1);
}
for (int i = 1;i <= m; i++) cout << ans[i] << '\n';
}