题意
一个序列,MMM个询问,每次求l∼rl\sim rl∼r之间的权值种类。
思路
莫队算法,将询问分成n\sqrt{n}n块,对于询问,如果lll在同一个块内,就根据rrr升序排序。
根据这样排序后暴力,时间复杂度可从O(nm)O(nm)O(nm)优化成O(n∗n)O(n*\sqrt{n})O(n∗n)
最重要的。让我们先谈论右指针。对于每个块,查询是递增的顺序排序,所以右指针(currentRcurrentRcurrentR)按照递增的顺序移动。在下一个块的开始时,指针可能在extreme endextreme\ endextreme end(最右端?) ,将移动到下一个块中的最小的RRR处。这意味着对于一个给定的块,右指针移动的量是O(N)O(N)O(N)。我们有O(N)O(\sqrt{N})O(N)块,所以总共是O(N∗N)O(N∗\sqrt{N})O(N∗N)。太好了!
让我们看看左指针怎样移动。对于每个块,所有查询的左指针落在同一个块中,当我们从一个查询移动到另个一查询左指针会移动,但由于前一个LLL与当前的LLL在同一块中,此移动是O(N)O(\sqrt{N})O(N)(块大小)的。在每一块中左指针的移动总量是O(Q∗N),QO(Q∗\sqrt{N}),QO(Q∗N),Q是落在那个块的查询的数量。对于所有的块,总的复杂度为O(M∗N)O(M*\sqrt{N})O(M∗N)。
就是这样,总复杂度为O((N+M)∗sqrtN)=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]);
}