【莫队】luogu_1972 BZOJ_1878 [SDOI2009]HH的项链

题意

一个序列, M M M个询问,每次求 l ∼ r l\sim r lr之间的权值种类。

思路

莫队算法,将询问分成 n \sqrt{n} n 块,对于询问,如果 l l l在同一个块内,就根据 r r r升序排序。

根据这样排序后暴力,时间复杂度可从 O ( n m ) O(nm) O(nm)优化成 O ( n ∗ n ) O(n*\sqrt{n}) O(nn )

最重要的。让我们先谈论右指针。对于每个块,查询是递增的顺序排序,所以右指针( c u r r e n t R currentR currentR)按照递增的顺序移动。在下一个块的开始时,指针可能在 e x t r e m e   e n d extreme\ end extreme end(最右端?) ,将移动到下一个块中的最小的 R R R处。这意味着对于一个给定的块,右指针移动的量是 O ( N ) O(N) ON。我们有 O ( N ) O(\sqrt{N}) ON 块,所以总共是 O ( N ∗ N ) O(N∗\sqrt{N}) ONN )。太好了!

让我们看看左指针怎样移动。对于每个块,所有查询的左指针落在同一个块中,当我们从一个查询移动到另个一查询左指针会移动,但由于前一个 L L L与当前的 L L L在同一块中,此移动是 O ( N ) O(\sqrt{N}) ON (块大小)的。在每一块中左指针的移动总量是 O ( Q ∗ N ) , Q O(Q∗\sqrt{N}),Q OQN Q是落在那个块的查询的数量。对于所有的块,总的复杂度为 O ( M ∗ N ) O(M*\sqrt{N}) OMN

就是这样,总复杂度为 O ( ( N + M ) ∗ s q r t N ) = O ( N ∗ N ) O((N+M)∗sqrt{N})=O(N∗\sqrt{N}) O((N+M)sqrtN)=O(NN )

这里用了奇偶性排序,可以优化复杂度。

代码

#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]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值