这题可以离线数组数组,在线主席树,也可以离线莫队。
虽然数据加强让莫队挂了,作为裸题还是可以拿来练练手
莫队算法是从已经求过答案的区间,通过移动指针来得到询问区间的答案,因为已经求过的区间可以不用重复求。如果按右区间排序,用双指针会发现右指针只会移一遍。
但左指针在最差的情况下会从一端移到另一端,复杂度依然是O(n*m)
引用分块的思维,按左端点值进行分块,在块内按右区间排序,在一个块内的所有询问右指针移动依然是O(n),而左指针移动降为O(m * sqrt(n)),再加上均摊的情况,整体复杂度大概在O(sqrt(n) * m)。
核心代码依然是通过移动指针得到询问区间的答案,套用到了分块的思维,使得复杂度降下来。
虽然套用了分块的算法,但写的时候并不用真的分块存储,可以直接在原序列上按分块来排序。
网上大把莫队算法的详解,这里就不讲了
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 10;
const int maxm = 5e5 + 10;
const int mx = 1e3 +10;
int cnt[maxn],a[maxm],ans[maxn];
int n,q;
struct ss{
int id,l,r;
}qes[maxm + 10];
int block,num,res;
int curleft = 0,curright = 0;
bool cmp(ss a,ss b) {
return (a.l / block == b.l / block) ? a.r < b.r : a.l < b.l;
}
void query(int l,int r) {
while(curleft < l) {
cnt[a[curleft]]--;
if(cnt[a[curleft]] == 0) res--;
curleft++;
}
while(curleft > l) {
curleft--;
cnt[a[curleft]]++;
if(cnt[a[curleft]] == 1) res++;
}
while(curright < r) {
curright++;
cnt[a[curright]]++;
if(cnt[a[curright]] == 1) res++;
}
while(curright > r) {
cnt[a[curright]]--;
if(cnt[a[curright]] == 0) res--;
curright--;
}
}
int main() {
scanf("%d",&n);
int mx = 0;
for(int i = 1; i <= n; i++) {
scanf("%d",&a[i]);
}
scanf("%d",&q);
for(int i = 1; i <= q; i++) {
scanf("%d%d",&qes[i].l,&qes[i].r);
qes[i].id = i;
}
block = n >= 2333 ? 2333 : n;
curleft = curright = 0;
res = 0;
sort(qes + 1,qes + q + 1,cmp);
for(int i = 1; i <= q; i++) {
int p = qes[i].id;
query(qes[i].l,qes[i].r);
ans[p] = res;
}
for(int i = 1; i <= q; i++) {
printf("%d\n",ans[i]);
}
return 0;
}