区间不同数
题目链接:ybt金牌导航6-4-1
题目大意
有一个序列,多次询问,每次问题一个区间内有多少种数。
思路
这题有很种做法,树状数组,主席树都可以。
但是因为这题数据比较小而且支持离线,我们用一个简单好些的算法——莫队。
莫队是什么呢?
它就是把你区间的左右边滑动,然后按一定的顺序滑到询问。
那边界滑动一个距离就是 (O(1)),那我们就像要怎么样才能让它滑动的次数更小。
那怎么弄呢?
有一种方法,就是分块,询问排序的第一关键字是左边所处的块的编号,第二关键字就是右边的编号。
然后左边的最多移动 (msqrt{n}) 次(每次都在块的最左最右处横跳,然后询问多少次跳多少次),右边的最多移动 (nsqrt{n}) 次(每次都在最前最后横跳,然后横跳 (sqrt{n}) 次左边的就到最后了)。
总复杂度为 (O((n+m)sqrt{n}))。
代码
#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
struct node {
int l, r, num;
}ask[500001];
int n, a[50001], K, answer, l, r;
int m, num[1000001], ans[200001];
int get_K(int x) {
return (x - 1) / K;
}
bool cmp(node x, node y) {
if (get_K(x.l) != get_K(y.l)) return get_K(x.l) < get_K(y.l);
return x.r < y.r;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
K = floor(sqrt(n));//分块
scanf("%d", &m);
for (int i = 1; i <= m; i++) {
scanf("%d %d", &ask[i].l, &ask[i].r);
ask[i].num = i;
}
sort(ask + 1, ask + m + 1, cmp);
//第一关键字:左边所属块的编号
//第二关键字:右边的编号
l = ask[1].l;//处理出一开始的
r = ask[1].r;
for (int i = l; i <= r; i++) {
num[a[i]]++;
if (num[a[i]] == 1) answer++;
}
ans[ask[1].num] = answer;
for (int i = 2; i <= m; i++) {
while (l < ask[i].l) {//滑动左边的
num[a[l]]--;
if (!num[a[l]]) answer--;
l++;
}
while (l > ask[i].l) {
l--;
num[a[l]]++;
if (num[a[l]] == 1) answer++;
}
while (r > ask[i].r) {//滑动右边的
num[a[r]]--;
if (!num[a[r]]) answer--;
r--;
}
while (r < ask[i].r) {
r++;
num[a[r]]++;
if (num[a[r]] == 1) answer++;
}
ans[ask[i].num] = answer;
}
for (int i = 1; i <= m; i++)
printf("%d
", ans[i]);
return 0;
}