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

13 篇文章 0 订阅
11 篇文章 0 订阅

区间不同数

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

题目大意

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

思路

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

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

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

那怎么弄呢?
有一种方法,就是分块,询问排序的第一关键字是左边所处的块的编号,第二关键字就是右边的编号。
然后左边的最多移动 m n m\sqrt{n} mn 次(每次都在块的最左最右处横跳,然后询问多少次跳多少次),右边的最多移动 n n n\sqrt{n} nn 次(每次都在最前最后横跳,然后横跳 n \sqrt{n} n 次左边的就到最后了)。

总复杂度为 O ( ( n + m ) n ) O((n+m)\sqrt{n}) O((n+m)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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值