HDU6959 zoto (莫队 + 值域分块)

题目链接: zoto

大致题意

给定一个长度为 n n n的序列, 有 m m m次询问, 每次询问给出l a r b.

表示询问在区间 [ l , r ] [l, r] [l,r]内, 值域 [ a , b ] [a, b] [a,b]之间有多少个不同数值.

和原题叙述略有不同, 但是本质是这样.

解题思路

莫队 + 值域分块

我们考虑要得到区间 [ l , r ] [l, r] [l,r]的信息, 我们可以通过莫队来实现 O ( n ) O(\sqrt{n}) O(n )的修改, O ( 1 ) O(1) O(1)的查询.

本题需要统计某个值域范围内有多少个不同的数值. 如果我们暴力一点, 用树状数组维护, 可以做到 O ( n l o g n ) O(\sqrt{n}logn) O(n logn)的修改, O ( 1 ) O(1) O(1)的查询.
但是这样做的话, 修改的复杂度太高了, 于是我们考虑把修改的复杂度摊在查询上.

我们可以通过对值域进行分块的方式, 去记录每个值域块内的元素种类, 这样可以做到 O ( n ) O(\sqrt{n}) O(n )的修改, O ( n ) O(\sqrt{n}) O(n )的查询.

值域分块这个东西还是比较好理解的, 如果不会的话, 学一下分块的基本思想, 然后对照代码看看就明白了.

➡️同类题目推荐⬅️

AC代码

#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 1E5 + 10, B = 320;
int w[N];
struct mo {
	int l, r, a, b, id;
	bool operator< (const mo& t) const {
		if (l / B != t.l / B) return l < t.l;
		return l / B & 1 ? r < t.r : r > t.r;
	}
}; vector<mo> area;


int res[N];
int cou[N], bcou[B];
void add(int c) { if (++cou[c] == 1) bcou[c / B]++; }
void sub(int c) { if (!--cou[c]) bcou[c / B]--; }

int ask(int a, int b) {
	int res = 0;
	if (a / B == b / B) {
		while (a <= b) res += cou[a++] != 0;
		return res;
	}
	int la = a / B, lb = b / B;
	while (a / B == la) res += cou[a++] != 0;
	while (b / B == lb) res += cou[b--] != 0;

	for (int i = a / B; i <= b / B; ++i) res += bcou[i];
	return res;
}
int main()
{
	int T; cin >> T;
	while (T--) {
		area.clear();

		int n, m; scanf("%d %d", &n, &m);
		rep(i, n) scanf("%d", &w[i]);
		rep(i, m) {
			int l, r, a, b; scanf("%d %d %d %d", &l, &a, &r, &b);
			area.push_back({ l, r, a, b, i });
		}
		sort(area.begin(), area.end());

		int L = 1, R = 0;
		for (auto& op : area) {
			int l = op.l, r = op.r, a = op.a, b = op.b, id = op.id;
			while (l < L) add(w[--L]);
			while (r > R) add(w[++R]);
			while (L < l) sub(w[L++]);
			while (R > r) sub(w[R--]);

			res[id] = ask(a, b);
		}

		while (L <= R) sub(w[L++]); // 清空信息
		rep(i, m) printf("%d\n", res[i]);
	}

	return 0;
}

END

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逍遥Fau

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值