传送:https://www.luogu.org/jump/spoj/3267
题意:
给定一个长度为$n$的序列,$q$个询问,问区间$[l,r]$内的不同数的个数。
数据范围:
$1<=n<=30000,1<=a_i<=10^6,1<=q<=200000$。
分析:
求区间内不同数的个数。很明显是莫队的裸题。
那么对于主席树怎么做呢?
统计不同数的个数,那么就是看一个数是否在同一区间内出现多次。
那么就可以记录一个数字后一个出现的位置,即第$i$个位置的数字后一个出现的位置为$nxt[i]$。
统计答案的时候就是可以求区间$[l,r]$内的$nxt[i]$是否有$<=r$的,这样就代表有重复。
换一句话就是求区间$[l,r]$内,$nxt[i]$在$[r+1,n+1]$范围内的个数。
那么主席树维护数组下标,每个主席树是权值线段树,代表后一个位置出现数字的个数。
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn=3e4+10; 4 struct node{ 5 int l,r,num; 6 }tree[maxn*100]; 7 int size=0,root[maxn],a[maxn],nxt[maxn]; 8 map<int,int> mp; 9 void update(int &x,int y,int l,int r,int dex){ 10 x=++size; tree[x]=tree[y]; tree[x].num++; 11 if(l==r) return ; 12 int mid=(l+r)>>1; 13 if (dex<=mid) update(tree[x].l,tree[y].l,l,mid,dex); 14 else update(tree[x].r,tree[y].r,mid+1,r,dex); 15 } 16 int query(int x,int y,int l,int r,int xx,int yy){ 17 if (xx<=l && r<=yy) return tree[y].num-tree[x].num; 18 int mid=(l+r)>>1; 19 int res=0; 20 if (xx<=mid) res+=query(tree[x].l,tree[y].l,l,mid,xx,yy); 21 if (yy>mid) res+=query(tree[x].r,tree[y].r,mid+1,r,xx,yy); 22 return res; 23 } 24 int main(){ 25 int n;scanf("%d",&n); 26 for (int i=1;i<=n;i++) scanf("%d",&a[i]); 27 mp.clear(); 28 for (int i=n;i>=1;i--){ 29 if (mp[a[i]]==0) nxt[i]=n+1; 30 else nxt[i]=mp[a[i]]; 31 mp[a[i]]=i; 32 } 33 for (int i=1;i<=n;i++){ 34 update(root[i],root[i-1],1,n+1,nxt[i]); 35 } 36 int m,x,y;scanf("%d",&m); 37 while(m--){ 38 scanf("%d%d",&x,&y); 39 int ans=query(root[x-1],root[y],1,n+1,y+1,n+1); 40 printf("%d ",ans); 41 } 42 return 0; 43 }