区间种类数问题
在线做法
利用可持久化线段树,从左至右依次扫一遍,因为我们只关心种类,故只需要保留最靠右的那一个数即可,其它的没用。查询时调用 \(root_r\) 所在的线段树。具体的,首先在下标 \(i\) 加 \(1\),找到前一个值为 \(a_i\) 的下标 \(pos_{a_i}\),将其减 \(1\),最后 \(pos_{a_i}=i\),这样就只保留最靠右的那一个数。查询时调用 \(root_r\) 的原因是:这颗线段树保留的是 \([1,r]\) 最靠右的数,无论 \(l\) 取到什么,\(sum[l,r]\) 都是区间种类数。
const int N=1e6+7;
int n,a[N],pos[N],root[N],tot;
struct Tree{
int sum[N*40],ls[N*40],rs[N*40];
void pushup(int p){sum[p]=sum[ls[p]]+sum[rs[p]];}
void modify(int q,int &p,int x,int k,int l=1,int r=n){
p=++tot;
ls[p]=ls[q];rs[p]=rs[q];sum[p]=sum[q];
if(l==r){sum[p]+=k;return;}
int mid=l+r>>1;
if(x<=mid)modify(ls[q],ls[p],x,k,l,mid);
else modify(rs[q],rs[p],x,k,mid+1,r);
pushup(p);
}
int query(int p,int ql,int qr,int l=1,int r=n){
if(ql<=l&&r<=qr)return sum[p];
int mid=l+r>>1,ans=0;
if(ql<=mid)ans+=query(ls[p],ql,qr,l,mid);
if(qr>mid)ans+=query(rs[p],ql,qr,mid+1,r);
return ans;
}
}tr;
int main(){
read(n);
for(int i=1;i<=n;i++)read(a[i]);
for(int i=1;i<=n;i++){
tr.modify(root[i-1],root[i],i,1);
if(pos[a[i]])tr.modify(root[i],root[i],pos[a[i]],-1);
pos[a[i]]=i;
}
int q;read(q);
while(q--){
int l,r;
read(l);read(r);
printf("%d\n",tr.query(root[r],l,r));
}
return 0;
}
离线做法
树状数组做法类似可持久化线段树,只不过是将询问按 \(r\) 排序,然后从小到大处理,就不必可持久化。
const int N=1e6+7;
int m,n,a[N],pos[N],c[N],ans[N];
struct bb{
int l,r,id;
bool operator<(const bb t)const{
return r<t.r;
}
}q[N];
int lowbit(int x){return x&(-x);}
void add(int x,int y){while(x<=n)c[x]+=y,x+=lowbit(x);}
int query(int x){int s=0;while(x)s+=c[x],x-=lowbit(x);return s;}
int main(){
read(n);
for(int i=1;i<=n;i++)read(a[i]);
read(m);
for(int i=1;i<=m;i++)
read(q[i].l),read(q[i].r),q[i].id=i;
sort(q+1,q+m+1);
int r=0;
for(int i=1;i<=m;i++){
while(r<q[i].r){
r++;add(r,1);
if(pos[a[r]])add(pos[a[r]],-1);
pos[a[r]]=r;
}
ans[q[i].id]=query(r)-query(q[i].l-1);
}
for(int i=1;i<=m;i++)
printf("%d\n",ans[i]);
return 0;
}
莫队做法这题就是板题,可以在我的 这篇博客 里查看。