请问你今天要来一份莫队吗?
因为之前bz讲过类似,用的是树状数组,所以这里就不打标题了,传送链接:HH的项链·第一弹[树状数组]
同样的我们依旧进行离线并对询问进行排序,以左端点所在块为第一关键字,以右端点为第二关键字。(这样可以减少大幅度移动次数)
我们以样例为例:
数据 | 1 | 2 | 3 | 4 | 3 | 5 |
---|---|---|---|---|---|---|
编号 | 1 | 2 | 3 | 4 | 5 | 6 |
块号 | 1 | 1 | 2 | 2 | 3 | 3 |
提问:
1.[1,2]
2.[2,6]
3.[3,5]
那么我们就要两个移动下标f1,f2。其实位置为0,0
随着问题的讨论而不断移动。具体为:f1,f2卡L和R,然后cnt[i]记录第i个数出现了多少次,根据f1和f2的移动加减,对于每个询问,就回答有多少个cnt不为零就可以啦。
Q时间复杂度会不会很大?
A:不会很大,只有在块移动后才会出现较大的移动,然而随着块的增大,块的移动范围会越来越小。所以不用担心时间的问题
注意:莫队的分块记得取n的2/3最快!
代码:(这是在luogu上过不了的莫队qwq)
#include<bits/stdc++.h>
using namespace std;
struct mapn
{int x,y,num;}dian[200000+5];
int n,m,k;
bool cmp(mapn a,mapn b)
{
if ((a.x-1)/k==(b.x-1)/k)
return a.y<b.y;
return (a.x-1)/k<(b.x-1)/k;
}
int a[50000+5],cnt[1000000+5],tot,ans[200000+5],f1,f2;
void change_y(int &x,int y)
{
while (x<y){x++;
if (cnt[a[x]]==0) tot++;
cnt[a[x]]++;
}
while (x>y){cnt[a[x]]--;
if (cnt[a[x]]==0) tot--;
x--;
}
}
void change_x(int &x,int y)
{
while (x<y){
x++;
cnt[a[x-1]]--;
if (cnt[a[x-1]]==0&&a[x]!=0) tot--;
}
while (x>y){
x--;
if (cnt[a[x]]==0)tot++;
cnt[a[x]]++;
}
}
int main()
{
//freopen("ceshi.in","r",stdin);
scanf("%d",&n);
k=floor(sqrt(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",&dian[i].x,&dian[i].y),dian[i].num=i;
sort(dian+1,dian+1+m,cmp);
for (int i=1;i<=m;i++)
{
change_y(f2,dian[i].y);
change_x(f1,dian[i].x);
ans[dian[i].num]=tot;
}
for (int i=1;i<=m;i++)
printf("%d\n",ans[i]);
return 0;
}