SPOJ

题意;

  给一个数列${ a_i}$ 一些询问$(l_i,r_i)$ 问你$[l,r]$有多少个不同元素

题解:
  其实本质思路和离线化处理询问的线段树/树状数组写法差不多,对区间$[x,r]$来说,所有数字只有$r$前面的最后一次出现才有意义

  于是想到通过记录树的版本来保存情况

  先保存数列,再对于每个数字依次建树,如果这个数字之前出现过,就把前一个树的该点删除,再插入新点,作为新树,否则正常键新树

#include <bits/stdc++.h>
#define nd seg[now]
#define ndp seg[pre]
#define mid ((s+t)>>1)
using namespace std;
const int maxn=1e5+10;
int casn,n,m,k;
int num[maxn],rt[maxn],size,pos[maxn];
struct node{
	int l,r,sum;
}seg[maxn*20];
map<int,int>vis;
void maketree(int s=1,int t=k,int &now=rt[0]){
	now=++size;nd={s,t,0};
	if(s==t) return ;
	maketree(s,mid,nd.l);maketree(mid+1,t,nd.r);
}
void update(int &now,int pre,int pos,int x,int s=1,int t=k){
	now=++size;
	nd=ndp,nd.sum+=x;
	if(s==t)return ;
	if(pos<=mid)update(nd.l,ndp.l,pos,x,s,mid);
	else update(nd.r,ndp.r,pos,x,mid+1,t);
}
int query(int now,int l,int r,int s=1,int t=k){
	if(l>t||r<s||l>r) return 0;
	if(l<=s&&r>=t) {
		return nd.sum;
	}
	return query(nd.l,l,r,s,mid)+query(nd.r,l,r,mid+1,t);
}
int main(){
	scanf("%d",&k);
	for(int i=1;i<=k;i++){
		scanf("%d",num+i);
		pos[i]=num[i];
	}
	vis.clear();
	size=0;
	maketree();
	int id=0;
	for(int i=1;i<=k;i++){
		if(vis[num[i]]!=0) {
			update(id,rt[i-1],vis[num[i]],-1);
			update(rt[i],id,i,1);
		}else update(rt[i],rt[i-1],i,1);
		vis[num[i]]=i;
	}
	scanf("%d",&m);
	while(m--){
		int a,b;
		scanf("%d%d",&a,&b);
		printf("%d
",query(rt[b],a,k));
	}
  return 0;
}
原文地址:https://www.cnblogs.com/nervendnig/p/9201515.html