把区间统计转化为前缀和,这里的前缀和不是普通的前缀和,对相同的ai,只有最右边那个才是有效的。
举个栗子:1 2 2 1 3 这样一个序列有效是这个样子 * * 2 1 3 ,因为1在后面出现过所以前一个无效,同理 2。前缀和则是0 0 1 2 3 ,那么对区间[1,5],[2,5],[3,5],[4,5],..[x,5] 我们都可以用sum[5]-sum[x-1]来求。
这里的一个问题是前缀和是对右端点确定的情况,所以只用一棵线段树或树状数组来维护的话就要离线,对查询的右端点进行排序。
如果强制在线怎么做呢,那就是主席树,简单理解为对每一个前缀建一棵线段树(这就是主席树做的事情),然后查询就是到右端点对应那棵树里去查询就ok了。
离线+树状数组
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <map>
using namespace std;
const int maxn=30000+5;
map<int,int> mp;
int data[maxn];
int a[maxn];
int ans[200000+5];
struct node{
int l,r,id;
bool operator<(node t)const{
return r<t.r;
}
}q[200000+5];
int sum(int i){
int ans=0;
while(i>0){
ans+=data[i];
i-=i&-i;
}
return ans;
}
void add(int i,int x){
while(i<maxn){
data[i]+=x;
i+=i&-i;
}
}
int main()
{
int n;
while(~scanf("%d",&n)){
fill(data,data+n+2,0);
mp.clear();
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
int m;
scanf("%d",&m);
for(int i=0;i<m;i++){
scanf("%d%d",&q[i].l,&q[i].r);
q[i].id=i;
}
sort(q,q+m);
int pre=1;
for(int i=0;i<m;i++){
for(int j=pre;j<=q[i].r;j++){
if(mp[a[j]]!=0){
add(mp[a[j]],-1);
}
add(j,1);
mp[a[j]]=j;
}
pre=q[i].r+1;
ans[q[i].id]=sum(q[i].r)-sum(q[i].l-1);
}
for(int i=0;i<m;i++){
printf("%d\n",ans[i]);
}
}
return 0;
}
主席树
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <map>
#define mid (l+r>>1)
using namespace std;
const int maxn=30000+5;
int a[maxn],root[maxn];
int data[maxn*20],ls[maxn*20],rs[maxn*20];
int cnt;
map<int,int> mp;
int build(int l,int r){
int t=cnt++;
data[t]=0;
if(l==r)return t;
ls[t]=build(l,mid);
rs[t]=build(mid+1,r);
return t;
}
int update(int p,int l,int r,int v,int w){
int t=cnt++;
data[t]=data[p]+w;
if(l==r) return t;
if(v<=mid){
rs[t]=rs[p];
ls[t]=update(ls[p],l,mid,v,w);
}
else{
ls[t]=ls[p];
rs[t]=update(rs[p],mid+1,r,v,w);
}
return t;
}
int query(int t,int l,int r,int v){
if(l==r)return data[t];
if(v<=mid)return query(ls[t],l,mid,v)+data[rs[t]];
else return query(rs[t],mid+1,r,v);
}
int main()
{
int n;
while(~scanf("%d",&n)){
cnt=1;mp.clear();
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
root[0]=build(1,n);
for(int i=1;i<=n;i++){
if(mp[a[i]]==0)
root[i]=update(root[i-1],1,n,i,1);
else {
int tmp=update(root[i-1],1,n,mp[a[i]],-1);
root[i]=update(tmp,1,n,i,1);
}
mp[a[i]]=i;
}
int m;
scanf("%d",&m);
for(int i=0;i<m;i++){
int l,r;
scanf("%d%d",&l,&r);
printf("%d\n",query(root[r],1,n,l));
}
}
return 0;
}