【ybt金牌导航6-4-1】区间不同数 / 莫队例题

区间不同数

题目链接:ybt金牌导航6-4-1

题目大意

有一个序列,多次询问,每次问题一个区间内有多少种数。

思路

这题有很种做法,树状数组,主席树都可以。

但是因为这题数据比较小而且支持离线,我们用一个简单好些的算法——莫队。

莫队是什么呢?
它就是把你区间的左右边滑动,然后按一定的顺序滑到询问。
那边界滑动一个距离就是 \(O(1)\),那我们就像要怎么样才能让它滑动的次数更小。

那怎么弄呢?
有一种方法,就是分块,询问排序的第一关键字是左边所处的块的编号,第二关键字就是右边的编号。
然后左边的最多移动 \(m\sqrt{n}\) 次(每次都在块的最左最右处横跳,然后询问多少次跳多少次),右边的最多移动 \(n\sqrt{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\n", ans[i]);
	
	return 0;
}
posted @ 2021-02-24 16:37  あおいSakura  阅读(47)  评论(0编辑  收藏  举报