题目链接:https://vjudge.net/problem/spoj-dquery
求区间不同数的总个数
n个数,数组a1...an,m次询问,区间al....ar中区间数的种数的总量
5
1 1 2 1 3
3
1 5
2 4
3 5
3
2
3
主席树:
每颗线段树存每个点最后出现的位置,答案等于当前线段树的区间和
离线求法:
对于求区间l~r的答案,等于1~r中的元素,每次最后一个点最后出现的位置+1
则sum[l,r]就是答案,用树状数组维护。
把所有区间按r从小到大排序,按上述方法(每次维护最后一个点出现的位置)
遍历数组,每次遍历到区间的r,求一下当前区间的答案。
下述为主席树代码
#include<bits/stdc++.h>
#define M 30005
using namespace std;
int n,m,len_disc,tot;
int A[M],disc[M],root[M];
map<int,int>mp;
struct segment_tire
{
int l,r,sum;
segment_tire(int _l=0,int _r=0,int _sum=0):l(_l),r(_r),sum(_sum){};
}T[20*M];
void init_discate()
{
for(int i=1;i<=n;i++)
disc[i]=A[i];
sort(disc+1,disc+1+n);
len_disc=unique(disc+1,disc+1+n)-disc-1;
}
int discate(int x)
{
return lower_bound(disc+1,disc+1+len_disc,x)-disc;
}
void update(int &rt,int old,int pos,int v,int l,int r)//从旧版本的pos位置更新v,存为新版本
{
T[++tot]=T[old];
T[tot].sum+=v;
rt=tot;
if(l==r) return ;
int mid=(l+r)>>1;
if(pos<=mid) update(T[rt].l,T[old].l,pos,v,l,mid);
else update(T[rt].r,T[old].r,pos,v,mid+1,r);
}
int query(int rt,int x,int y,int l,int r)//查询当前线段树区间l~r的和
{
if(x<=l&&r<=y) return T[rt].sum;
int mid=(l+r)>>1,ans=0;
if(x<=mid) ans+=query(T[rt].l,x,y,l,mid);
if(y>mid) ans+=query(T[rt].r,x,y,mid+1,r);
return ans;
}
void init()
{
tot=0;
T[0]=segment_tire(0,0,0);
mp.clear();
root[0]=0;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&A[i]);
init_discate();
init();
for(int i=1;i<=n;i++)
{
//主席树中存的是最后一次出现位置的下标
int dis=discate(A[i]);
if(!mp[dis])//没出现过,当前位置+1
{
update(root[i],root[i-1],i,1,1,n);
mp[dis]=i;
}
else//否则,更新最后一次出现的位置
{
update(root[i],root[i-1],mp[dis],-1,1,n);
update(root[i],root[i],i,1,1,n);
mp[dis]=i;
}
}
int l,r;
scanf("%d",&m);
while(m--)
{
scanf("%d%d",&l,&r);
printf("%d\n",query(root[r],l,r,1,n));//查询1~r版本的线段树l~r区间的和
}
}