思路:
本质是求区间数量大于1的数的个数,考虑一个 next 数组记录相同数最近的下一个位置(经典套路)查询区间离线后 l 从小到大排序,要删掉 l 上的数 如果下一个数存在就要减去下一个数的贡献,如果下下一个数存在就要加上下下一个数的贡献,实现用树状数组,具体看代码
一些注意点:
数组位置建议从1开始,如果从0开始,树状数组遇到0要特判
这个问题是不满足区间相减相加的,很多文章在查询答案时把
ans=query(q[i].r);
写成了
ans=query(q[i].r)-query(q[i].l-1);
答案不会出错但说明还是不理解, 在每次查询时 l 指针已经和区间左端点对齐了,query(q[i].l-1)的答案恒是0
莫队的思想,只是用树状数组实现罢了
参考代码:
int n,c,m;
int a[N],_next[N],_last[N],sum[N],ans[N]; // next 记录下一个相同颜色的位置 last 是上一个
struct PW{ //查询排序
int l,r,id;
}q[N];
bool cmp(PW s1,PW s2){
return s1.l<s2.l;
}
int lowbit(int x){
return x&(-x);
}
void add(int x,int num){
if(x==0) return ;
for( ;x<=n;sum[x]+=num,x+=lowbit(x));
}
int query(int x){
int re=0;
for( ;x>0;re+=sum[x],x-=lowbit(x));
return re;
}
int main(){
cin>>n>>c>>m; // n个数 c种 m个查询
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=n;i>=1;i--){ // 记录相同数的最近的下一个位置
_next[i]=_last[a[i]];
_last[a[i]]=i;
}
for(int i=1;i<=c;i++){ // 出现数量大于1在第二次出现的地方加贡献
if(_last[i]&&_next[_last[i]]) add(_next[_last[i]],1);
}
for(int i=1;i<=m;i++){
cin>>q[i].l>>q[i].r;
q[i].id=i;
}
sort(q+1,q+1+m,cmp);
int l=1;
for(int i=1;i<=m;i++){
while(l<q[i].l){
if(_next[l]) add(_next[l],-1); // l删去下一个位置的贡献减去
if(_next[l]&&_next[_next[l]]) add(_next[_next[l]],1); //下下个位置存在加贡献
l++;
}
ans[q[i].id]=query(q[i].r);
}
for(int i=1;i<=m;i++){
cout<<ans[i]<<endl;
}
return 0;
}