主席树的另一种用途,,(还有一种是求区间第k大,区间<=k的个数)
事实上:每个版本的主席树维护了每个值最后出现的位置
这种主席树不是以权值线段树为基础,而是以普通的线段树为下标的
/* 无修改,求区间[l,r]有多少个不同的数 主席树的另外一种姿势: 不像求区间第k大,区间<=k的数有几个之类的可持久化权值线段树,每个结点维护的是当前版本x的出现次数 本题的主席树每个结点维护当前版本下位置i的值的出现情况 更新:以数组a为基础建立线段树,然后从左往右扫描a 当扫到ai时,如果ai是第一次出现,那么直接在新的线段树上在i位置 +1 如果值ai在前面位置p出现过,那么在新的线段树上将 p位置 -1,在i位置 +1 询问:[l,r] 首先明确第r棵线段树的rt[1]是[1,r]区间上的不同的数的个数 现在要求[l,r]上不同的数的个数,那就要把第r棵线段数[l,r]区间的和求出来 所以只要询问第r棵线段树区间[l,n]的和即可 可以发现本题并没有用到类似前缀和的思想,因为只要保存各个版本的主席树即可 */ #include<bits/stdc++.h> using namespace std; #define maxn 1000005 struct Node{int lc,rc,sum;}t[100005*25]; int n,a[maxn],rt[maxn],size,q,pre[maxn],last[maxn]; int build(int l,int r){ int now=++size; t[now].lc=t[now].rc=t[now].sum=0; if(l==r)return now; int mid=l+r>>1; t[now].lc=build(l,mid); t[now].rc=build(mid+1,r); return now; } int update(int last,int pos,int val,int l,int r){//位置pos+val int now=++size; t[now]=t[last];t[now].sum+=val; if(l==r)return now; int mid=l+r>>1; if(pos<=mid)t[now].lc=update(t[last].lc,pos,val,l,mid); else t[now].rc=update(t[last].rc,pos,val,mid+1,r); return now; } int query(int rt,int L,int R,int l,int r){//查询[L,R]的区间和 if(L<=l && R>=r) return t[rt].sum; int mid=l+r>>1,res=0; if(L<=mid)res+=query(t[rt].lc,L,R,l,mid); if(R>mid)res+=query(t[rt].rc,L,R,mid+1,r); return res; } int main(){ cin>>n; for(int i=1;i<=n;i++){ scanf("%d",&a[i]) ; pre[i]=last[a[i]];//上一次出现位置 last[a[i]]=i; } rt[0]=build(1,n); for(int i=1;i<=n;i++){ if(pre[i]==0)rt[i]=update(rt[i-1],i,1,1,n);//这个位置+1 else{ int tmp=update(rt[i-1],pre[i],-1,1,n); rt[i]=update(tmp,i,1,1,n); } } cin>>q; while(q--){ int l,r; scanf("%d%d",&l,&r); //cout<<t[rt[r]].sum<<' '; cout<<query(rt[r],l,n,1,n)<<' '; } }