- 题意
给你一个数列,询问(l,r)中不同种类的数的个数。
- 题解
很经典的莫队算法,这道题可以说是板题了。
所谓莫队就是将询问排序,这次的询问部分信息与上次询问一致,来尽量压缩时间,对于一次询问(l1,r1),由上次询问(l2,r2),只需移动|l1-l2|+|r1-r2|步即可。
对于这道题,对序列分块进行适当分块,再对于询问(l,r)区间排序可以做到 O(nn√) 的时间复杂度。(当然你发现其实询问是两点的曼哈顿距离,然后写个曼哈顿树会更优,不过复杂度上没什么显著的提高,且曼哈顿树实现较为困难,所以不再赘述)。
分块的方法:
不妨设块大小为S,块的个数为k(k*S=n)。
对于询问排序,先看左端点所在的区间,再看右端点的大小排序。
对于右端点,在左端点在一个块内时,右端点单调向右移动,最多移动
n
步。左端点所在块是单调递增的,共有
对于左端点,如果区间不动每次询问会在区间内移动
S
次(对于跨过区间的步数,因为不会回到原来的块,所以大致一共会移动
设两种移动大致相等,由均值思想,当 n∗k≈S∗m 时间复杂度较为优秀,即是 S≈100 。(实测 S=100 耗时1376 ms, S=n√≈200 耗时1732 ms)。
当然,其实块的大小大部分情况下还是取 n√ 较好(当然,这只是时间复杂度做到 O(nn√) 时的情况,如果再加个 log 什么的还是打表找最小值吧)。
- Code:
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
const int Maxn=5e4+50;
const int Maxm=2e5+50;
const int Maxval=1e6+50;
const int S=100;
inline int read()
{
char ch=getchar();
int i=0,f=1;
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){i=(i<<1)+(i<<3)+ch-'0';ch=getchar();}
return i*f;
}
int buf[50];
inline void W(int x)
{
if(!x)putchar('0');
if(x<0)putchar('-'),x=-x;
while(x)buf[++buf[0]]=x%10,x/=10;
while(buf[0])putchar('0'+buf[buf[0]--]);
}
int n,m,a[Maxn],cnt[Maxval],ans[Maxm];
struct node
{
int l,r,id;
friend inline bool operator < (const node &a,const node &b)
{
int s1=(a.l-1)/S+1,s2=(b.l-1)/S+1;
if(s1!=s2)return s1<s2;
return a.r<b.r;
}
}q[Maxm];
int main()
{
n=read();
for(int i=1;i<=n;i++)a[i]=read();
m=read();
for(int i=1;i<=m;i++)q[i].l=read(),q[i].r=read(),q[i].id=i;
sort(q+1,q+m+1);
int L=0,R=0;
for(int i=1;i<=m;i++)
{
int l=q[i].l,r=q[i].r;
while(l<L)if(!cnt[a[--L]]++)ans[q[i].id]++;
while(l>L)if(!--cnt[a[L++]])ans[q[i].id]--;
while(r<R)if(!--cnt[a[R--]])ans[q[i].id]--;
while(r>R)if(!cnt[a[++R]]++)ans[q[i].id]++;
ans[q[i].id]+=ans[q[i-1].id];
}
for(int i=1;i<=m;i++)W(ans[i]),putchar('\n');
}