主席树在每次更新的叶子节点时,记录从根节点开始一路到叶子节点的更改节点信息。每个节点值更新后,还可以通过该节点更新前的根节点查询之前的信息。
如下图所示:
入门题目:给定n个数,q个询问,每次回答某个区间内不相同的元素个数。n, q(1 <= n, q <= 100000),n个正整数a_1, a_2, a_3, …, a_n(对于任意的1 <= i <= n,都有1 <= a_i <= n)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll Inf=1e16;
const int N=100005;
int vis[N];
struct node{
int l,r;
int num;
}tr[N*40];
int rt[N];
int cnt=0;
inline void push_up(int k){
tr[k].num=tr[tr[k].l].num+tr[tr[k].r].num;
}
int creat(int l,int r){
int k=cnt++;
if(l==r){
tr[k].num=0;
return k;
}
int mid=(l+r)>>1;
tr[k].l=creat(l,mid);
tr[k].r=creat(mid+1,r);
push_up(k);
return k;
}
int update(int o,int l,int r,int pos,int val){
int k=cnt++;
tr[k]=tr[o];
if(l==r){
tr[k].num+=val;
return k;
}
int mid=(l+r)>>1;
if(pos<=mid){
tr[k].l=update(tr[o].l,l,mid,pos,val);
}
else{
tr[k].r=update(tr[o].r,mid+1,r,pos,val);
}
push_up(k);
return k;
}
int query(int o,int l,int r,int pl,int pr){ //树根标号,区间范围[],查询区间范围[]
if(pl>pr){
return 0;
}
if(pl==l&&pr==r){
return tr[o].num;
}
int mid=(l+r)>>1;
if(pr<=mid){
return query(tr[o].l,l,mid,pl,pr);
}
else if(pl>mid){
return query(tr[o].r,mid+1,r,pl,pr);
}
else{
return query(tr[o].l,l,mid,pl,mid)+query(tr[o].r,mid+1,r,mid+1,pr);
}
}
int main(){
#ifdef _DEBUG
freopen("in.txt","r",stdin);
#endif // _DEBUG
int n,q;
cin>>n>>q;
rt[0]=creat(1,n); //建立空树
for(int i=1;i<=n;++i){
int t,pid=rt[i-1];
cin>>t;
if(vis[t]){ //出现过的数字,只能算一次,把上次出现的次数抵消
pid=update(pid,1,n,vis[t],-1);
}
rt[i]=update(pid,1,n,i,1); //记录第i颗树的根节点
vis[t]=i;
}
int ans;
while(q--){
int l,r;
cin>>l>>r;
ans=query(rt[r],1,n,l,r); //查询第r颗树[l,r]区间值
cout<<ans<<endl;
}
return 0;
}