[TK] HH的项链 离线树状数组解法
实际上这题很难和树状数组联系起来,我感觉效率也不是很高,感觉不是正解
怎么使用树状数组
这道题我们很容易想到一点:同种物品在一个区间内只能出现一次,先不考虑别的问题,我们想出下面这种使用树状数组的思路:
- 定义树状数组只有 \(0\) 和 \(1\) 两种状态,分别表示统计这个物品或不统计这个物品.
- 从前往后统计物品,即遍历赋值 \(1\) .
- 记录上一个和当前物品种类相同的物品在哪里,假如我们统计了当前物品,就把上一个相同种类的物品置 \(0\) .
- 区间 \([i,j]\) 内的物品种类数即为 \(sum(i,j)\).
但是这样做有一个问题,假如上一个相同种类的物品在区间内,但是当前这一个不在. 那么我们统计时,这个物品种类已经置 \(0\) , 就会出现错误.
离线
综上,我们现在需要解决一个问题:
统计当前区间时,这个区间内的物品既不能未统计,也不能已置 \(0\) .
我们可以这样解决:
- 记录每个区间的右端点,并从小到大排序.
- 取出一个最小的右端点,若当前对物品的遍历到达了这个右端点,那么马上算出答案并存储.
- 重复上述步骤.
这样一来,因为置 \(0\) 影响到的区间已经被事先算出来了,所以丝毫没有受到影响.
事实上,我们用到了一种算法思想即离线算法:先存储所有问题,待全部询问结束后再全部输出.
细节处理
1. 关于区间遍历
我们可以开一个变量 \(now\) 作为指针来记录当前遍历到的区间,若答案被算出,则 \(now++\) . 考虑到有右端点重复的区间,因此我们的写法如下:
while(a[now].r==i){
算出答案并存储;
now++;
}
2. 关于答案存储
我们可以给区间开一个结构体,这个结构体里除了有区间的左右端点之外,还有一个区间的询问顺序编号. 按这个编号,我们可以做到有序输出.
代码实现
注:本代码使用 封装树状数组
BIT t;
int s[1000001],fore[1000001],ans[1000001]; //s为原序列,fore记录了上一个相同种类的序号,ans记录答案
struct area{
int l,r,id;
bool operator <(const area &A)const{
return r<A.r;
}
}a[1000001];//记录区间
int n,anow=1,m;
int main(){
cin>>n;
t.n=n;
for(int i=1;i<=n;++i){
cin>>s[i];
}
cin>>m;
for(int i=1;i<=m;++i){
cin>>a[i].l>>a[i].r;
a[i].id=i;
}
sort(a+1,a+m+1);
for(int i=1;anow<=m&&i<=n;++i){
t.change(i,1);
if(fore[s[i]]){
t.change(fore[s[i]],-1);
}
fore[s[i]]=i;
while(a[anow].r==i){
ans[a[anow].id]=t.sum(a[anow].l,a[anow].r);
anow++;
}
}
for(int i=1;i<=m;++i){
cout<<ans[i]<<endl;
}
}