主席树——求区间[l,r]不同数字个数的模板

主席树的另一种用途,,(还有一种是求区间第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<<'\n';
        cout<<query(rt[r],l,n,1,n)<<'\n';
    }
}
 

猜你喜欢

转载自www.cnblogs.com/zsben991126/p/10750164.html
今日推荐