题目大意
对于数列 a[1⋯n]a[1⋯n],静态查询 [i,j][i,j] 中不同数的个数。
数据范围 1⩽n⩽500000,1⩽m⩽200000,0⩽ ai ⩽10000001⩽n⩽500000,1⩽m⩽200000,0⩽ ai ⩽1000000
题解
定义 fi,jfi,j 表示考虑前 ii 个方格, 位置为是否为最后一个出现的 ajaj。
则对于区间 [a,b][a,b] ,则显然地,答案就是 ∑i=abfb,i∑i=abfb,i
因为是区间求和,考虑前缀和降低复杂度。可是计算 fi,jfi,j 的时间复杂度为 O(n2)O(n2) ,已经超时了。
注意到,可以用 fifi 序列直接算出 fi+1fi+1 序列。
具体方法是记录下上一个 ajaj 的位置 lastajlastaj,则可以直接得到 fi+1,lastaj=0,fi+1,aj=1fi+1,lastaj=0,fi+1,aj=1,其他元素满足fi+1=fifi+1=fi。
其实也可以直接覆盖 fi,jfi,j,把 ii 那一维降掉。
但这样计算前缀和就不太靠谱了,可以换成树状数组或者线段树,考虑到是改点求段,因此选用树状数组。
因此可以 离线计算。区间按照右端点排序。每次计算向右拓展。然后依次回答右端点相同的询问。时间复杂度为 。
PS:考虑到 ai>nai>n,因此可以对数据进行离散化。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=500010;
const int maxm=200010;
const int maxi=1000010;
#define lowbit(x) ((x)&(-(x)))
int c[maxn],n;//树状数组相关
inline void add(int x,int d){
for(;x<=n;x+=lowbit(x))
c[x]+=d;
}
inline int sum(int x){
int ans=0;
for(;x>0;x-=lowbit(x))
ans+=c[x];
return ans;
}
inline int sum(int x,int y){
return sum(y)-sum(x-1);
}
struct qual{//区间相关
int l,r,i;
inline bool operator<(const qual &x)const{//排序相关
return r<x.r;
}
}e[maxm];
int a[maxn],m;
int last[maxi];//上一个a[i]的位置
int ans[maxn];//对答案排序(离线)
int main(void)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
scanf("%d",&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&e[i].l,&e[i].r);
e[i].i=i;//离线相关
}
sort(e+1,e+1+m);
int lasr=0;
memset(c,0,sizeof c);
for(int i=1;i<=m;i++){//枚举区间
while(lasr<e[i].r){//扩展右端点
++lasr;
if(last[a[lasr]])//修改为0
add(last[a[lasr]],-1);
add(lasr,1);//修改为1
last[a[lasr]]=lasr;//标记
}
ans[e[i].i]=sum(e[i].l,e[i].r);//记录答案
}
for(int i=1;i<=m;i++)
printf("%d\n",ans[i]);
return 0;
}