题意
一个序列, M M M个询问,每次求 l ∼ r l\sim r l∼r之间的权值种类。
思路
莫队算法,将询问分成 n \sqrt{n} n块,对于询问,如果 l l l在同一个块内,就根据 r r r升序排序。
根据这样排序后暴力,时间复杂度可从 O ( n m ) O(nm) O(nm)优化成 O ( n ∗ n ) O(n*\sqrt{n}) O(n∗n)
最重要的。让我们先谈论右指针。对于每个块,查询是递增的顺序排序,所以右指针( c u r r e n t R currentR currentR)按照递增的顺序移动。在下一个块的开始时,指针可能在 e x t r e m e e n d extreme\ end extreme end(最右端?) ,将移动到下一个块中的最小的 R R R处。这意味着对于一个给定的块,右指针移动的量是 O ( N ) O(N) O(N)。我们有 O ( N ) O(\sqrt{N}) O(N)块,所以总共是 O ( N ∗ N ) O(N∗\sqrt{N}) O(N∗N)。太好了!
让我们看看左指针怎样移动。对于每个块,所有查询的左指针落在同一个块中,当我们从一个查询移动到另个一查询左指针会移动,但由于前一个 L L L与当前的 L L L在同一块中,此移动是 O ( N ) O(\sqrt{N}) O(N)(块大小)的。在每一块中左指针的移动总量是 O ( Q ∗ N ) , Q O(Q∗\sqrt{N}),Q O(Q∗N),Q是落在那个块的查询的数量。对于所有的块,总的复杂度为 O ( M ∗ N ) O(M*\sqrt{N}) O(M∗N)。
就是这样,总复杂度为 O ( ( N + M ) ∗ s q r t N ) = O ( N ∗ N ) O((N+M)∗sqrt{N})=O(N∗\sqrt{N}) O((N+M)∗sqrtN)=O(N∗N)
这里用了奇偶性排序,可以优化复杂度。
代码
#include<cmath>
#include<cstdio>
#include<algorithm>
struct node {
int l, r, pos;
}que[500001];
int n, m, block, ans;
int a[500001], cnt[1000001], res[500001];
bool operator <(const node &x, const node &y) {
return (x.l / block) ^ (y.l / block) ? x.l < y.l : (((x.l / block) & 1) ? x.r < y.r : x.r > y.r);
}
void add(int x) {
if (++cnt[a[x]] == 1) ans++;
}
void del(int x) {
if (!--cnt[a[x]]) ans--;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
scanf("%d", &m);
block = n / sqrt(m * 2 / 3);
for (int i = 1; i <= m; i++)
scanf("%d %d", &que[i].l, &que[i].r), que[i].pos = i;
std::sort(que + 1, que + m + 1);
int l = 0, r = 0;
for (int i = 1; i <= m; i++) {
int ql = que[i].l, qr = que[i].r;
while (l < ql) del(l++);
while (l > ql) add(--l);
while (r < qr) add(++r);
while (r > qr) del(r--);
res[que[i].pos] = ans;
}
for (int i = 1; i <= m; i++)
printf("%d\n", res[i]);
}