由于询问的是区间中贝壳的种类数,所以问询区间中相同种类的贝壳只有一个会起作用
将i位置的贝壳前一次出现的位置记作pre[i],种类为x的贝壳最后一次出现的位置记作f[x],类似于邻接表的nxt和had,利用pre[i]=f[x],f[x]=i来处理pre
对于每个询问[l,r],只有pre[i]<l(l<=i<=r)的贝壳才会被算进答案(这里可以自己想一想为什么是这样)
也就是说所处位置为i的贝壳要想起作用,需要满足两个条件:条件① i必须满足l<=i<=r 条件② pre[i]必须满足pre[i]<l
我们可以将位置i是第j个询问[l,r]的边界称为位置i和询问j相对应(1<=i<=n,1<=j<=m)
显然一个位置可以对应任意个询问(这个问题会在后面解决),一个询问只能对应两个位置
可以用离线算法,用一个数组vec[i]来记录位置和对应的询问的关系(由于会用到vector,所以我用vec来命名)
一个vec[i]包含两个信息:
①vec[i].num表示位置i所对应的询问所对应的有关数据(这不废话吗 -_-||)(这句话是什么意思呢?假设位置i对应了询问j,而且位置i是询问j的左边界,那么vec[i].num=j*2,如果位置i是询问j的右边界,那么vec[i].num=j*2+1。那这是干什么用的呢?)
②vec[i].to表示位置i所对应的区间[l,r]的左边界l-1,即vec[i].to=l-1(这是干什么用的呢?由于被选中的贝壳必须要满足上面提到的两个条件,所以对于每个询问,只需要用树状数组查询有多少个pre[i]满足0<=pre[i]<=l-1(l<=i<=r)即可)
由于一个位置可以对应任意个询问,所以可以在原来的vec[i]上再加一维vec[i][j],j表示与位置i所对应的多个询问中的第j个询问(0<=j<vec[i].size())
在上面提到:对于每个询问,只需要用树状数组查询有多少个pre[i]满足0<=pre[i]<=l-1(l<=i<=r)即可,所以树状数组的query(x)表示满足0<=pre[i]<=x的i的个数(也可以是满足上述条件的pre[i]的个数,其实都一样的,因为一个i对应一个pre[i]嘛)(这里i的范围可以有两种,对应着两种操作:操作① 一次性把所有的满足0<=pre[i]<=x的i的个数都加进树状数组中,然后再扫描询问计算每个询问的结果,这样的话i的范围是(1<=i<=n)。操作② 边扫描每个询问边把pre[i]加进树状数组,这样的话i的范围就是(1<=i<=p)
那么操作①和操作②到底选哪种,就取决于我们需要i满足什么样的范围,满足0<=pre[i]<=l-1(l<=i<=r)的i的数量可以用满足0<=pre[i]<=l-1(1<=i<=r)的i的数量减去满足0<=pre[i]<=l-1(1<=i<=l-1)的i的数量得到
由于上面所需的两个范围(1<=i<=r)和(1<=i<=l-1)的两个上界r和l-1都不等于n,所以我们选用操作②
具体实现:用指针i扫描1到n每个位置,边扫边把pre[i]加到树状数组里,如果当前的位置i是询问j的左边界,此时vec[i][j].to为l-1,那么query(vec[i][j].to)的值就是满足0<=pre[i]<=l-1(1<=i<=l-1)的i的数量,这时候vec[i][j].num的作用就体现出来了,因为位置i是询问j的左边界,所以vec[i][j].num的值为j*2,用一个数组ans来记录每个询问的答案信息,对于每个询问j,将ans[j*2]赋值为query(l-1),即ans[vec[i][j].num]=query(vec[i][j].to)。同理,当指针i为询问j的右边界r时,query(vec[i][j].to)的值就是满足0<=pre[i]<=l-1(1<=i<=r)的i的数量,vec[i][j]的值就是j*2+1,进行相同的处理,等到全部扫描完时,枚举每个询问,这时询问j的答案就是ans[j*2+1]-ans[j*2],输出即可。
(由于某种贝壳第一次出现时此位置的pre值为0,且lowbit(0)等于0,在query()函数中会死循环,所以在调用和pre有关的判断时都在原本的值上+1,即可解决,共有两处需要加一,一个是将pre加入树状数组时给pre加一,一个是判断pre是否满足0<=pre<=l-1时给右边界加一(为什么左边界不用加一?这里可以自己想一想))
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
using namespace std;
int read()
{
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9')
{
if(c=='-') f=-1;
c=getchar();
}
while(c>='0'&&c<='9')
{
x=x*10+c-'0';
c=getchar();
}
return f*x;
}
const int N=1e6+5;
int tre[N],pre[N],f[N],a1,ans[N],n,m,l,r;
struct tree
{
int num,to;
};
vector <tree> vec[N];
int lowbit(int x)
{
return x&(-x);
}
void update(int x,int y)
{
for(;x<=n;x+=lowbit(x)) tre[x]+=y;
}
int query(int x)
{
int sum=0;
for(;x;x-=lowbit(x)) sum+=tre[x];
return sum;
}
int main()
{
n=read();
for(int i=1;i<=n;i++)
{
a1=read();
pre[i]=f[a1];
f[a1]=i;
}
m=read();
for(int i=1;i<=m;i++)
{
l=read(); r=read();
vec[l-1].push_back((tree){i*2,l-1});
vec[r].push_back((tree){i*2+1,l-1});
}
for(int i=1;i<=n;i++) //这里的i就是上文的指针p
{
update(pre[i]+1,1); //操作②,这里pre要+1
for(int j=0;j<vec[i].size();j++)
ans[vec[i][j].num]=query(vec[i][j].to+1); //这里用来判断的边界to要+1
}
for(int i=1;i<=m;i++) cout<<ans[i*2+1]-ans[i*2]<<"\n";
return 0;
}