[AHOI2013]作业 值域分块+莫队

题目描述

此时己是凌晨两点,刚刚做了 (Codeforces) 的小 (A) 掏出了英语试卷。英语作业其实不算多,一个小时刚好可以做完。然后是一个小时可以做完的数学作业,接下来是分别都是一个小时可以做完的化学,物理,语文……小 (A) 压力巨大。

这时小 (A) 碰见了一道非常恶心的数学题,给定了一个长度为 (n) 的数列和若干个询问,每个询问是关于数列的区间表示数列的第 (l) 个数到第 (r) 个数),首先你要统计该区间内大于等于 (a),小于等于 (b) 的数的个数,其次是所有大于等于 (a),小于等于 (b) 的,且在该区间中出现过的数值的个数。

(A) 望着那数万的数据规模几乎绝望,只能向大神您求救,请您帮帮他吧。

输入格式

第一行两个整数 (n,m)

接下来 (n) 个不超过 (10^5) 的正整数表示数列

接下来 (m) 行,每行四个整数 (l,r,a,b),具体含义参见题意。

输出格式

输出 (m) 行,分别对应每个询问,输出两个数,分别为在 (l)(r) 这段区间中大小在 ([a,b]) 中的数的个数,以及大于等于 (a),小于等于 (b) 的,且在该区间中出现过的数值的个数(具体可以参考样例)。

输入输出样例

输入

3 4
1 2 2
1 2 1 3
1 2 1 1
1 3 1 3
2 3 2 3

输出

2 2
1 1
3 2
2 1

说明/提示

(Nleqslant 100000,Mleqslant 100000),读入的数字均为 ([1,10^5]) 内的正整数。

分析

写了这个题感觉对莫队和分块理解深了许多(暴力算法妙啊!!)

首先看到区间查询,友好又好写的莫队当然是首要选择。其次我们看到,这个题的询问不同平常的莫队,答案要输出当前区间范围内,一个值域范围内有几种数和几个数,那么我们考虑如何快速计算当前区间范围内,有多少个值满足要求,那么这就要求我们在查询的时候,不仅仅需要更新区间,还需要查询某一个值域范围内的东西。显然可以用树状数组写,但是个人感觉维护起来不好写,而且会被卡……所以我们需要用到毒瘤的值域分块来进行维护。

首先是莫队,不会的可以看我之前的笔记(打广告)。重点在于我们在跳区间的时候如何修改,还有最后如何查询答案。由于每一次询问的 ([a,b]) 是一个区间,所以我们就可以把分块按值域开,然后我们开一个 (cnt) 数组用来记录每个值出现的次数,然后再用两个记录数组来分别记录当前值域块内有几种数和几个数。

在每一次区间边界改变的时候,需要加入当前数的话,我们先判断一下之前当前数有几个,如果为 (0) ,那么当前值所在的块的数的种类数就加 (1) ,然后给当前块的数总数加 (1) 。删除的时候也一样,如果 (cnt[i]--) 后为 (0) ,那么证明这个数在当前区间内没了,那么就在块的种类数里减 (1) 。最后我们只需要用分块的思想对于 ([a,b]) 区间进行统计答案即可。

需要注意的是,我们块是按照值域开的。所以要找到查询的值域和数列中的最大值进行分块,如果直接在数列中最大值进行分块的话,那么查询如果超过这个值,那么块的所属就会是 (0) ,那么统计答案就会挂(因为这个调了半个下午,感谢 lgx 巨佬),所以要取所有值域的最大值(可能这个问题是题库中数据问题,但是题目没有明确说明查询区间是多大,所以还是谨慎为好,不考虑这个问题在洛谷上是可以 (AC) 的)。

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e6+10;
#define gc() (p1 == p2 ? (p2 = buf + fread(p1 = buf, 1, 1 << 20, stdin), p1 == p2 ? EOF : *p1++) : *p1++)
#define read() ({ register int x = 0, f = 1; register char c = gc(); while(c < '0' || c > '9') { if (c == '-') f = -1; c = gc();} while(c >= '0' && c <= '9') x = x * 10 + (c & 15), c = gc(); f * x; })
char buf[1 << 20], *p1, *p2;
int a[maxn];
int n,m;
int jl1[maxn],jl2[maxn];//ji1为种类数,jl2为数的个数之和
int sum;
int cnt[maxn];
int ans1[maxn],ans2[maxn];
int block,bel[maxn];
struct Node{//离线询问
	int l,r,a,b,id;
	Node(){}
	Node(register int x,register int y,register int c,register int d,register int e){
		l = x,r = y,a = c,b = d,id = e;
	}
	friend bool operator < (register const Node& x,register const Node& y){//按块排序
		return bel[x.l] == bel[y.l] ? x.r < y.r : bel[x.l] < bel[y.l];
	}
}q[maxn];
struct Q{//开结构体记录答案
	int sum,lei;
	Q(){}
	Q(int a,int b){
		sum = a,lei = b;
	}
};
inline void Add(int pos){//加入一个数
	if(!cnt[a[pos]])jl1[bel[a[pos]]]++;//如果之前没有就种类++
	jl2[bel[a[pos]]]++;//数的总数++
	cnt[a[pos]]++;
}
inline void Del(int pos){//删除一个数
	cnt[a[pos]]--;
	if(!cnt[a[pos]])jl1[bel[a[pos]]]--;//删除后没了,当前块种类数--
	jl2[bel[a[pos]]]--;//当前块数的总数--
}
inline Q query(int l,int r){//分块查询值域范围内的答案
	int su = 0,le = 0;
	if(bel[l] == bel[r]){
		for(int i = l;i <= r;++i){
			if(cnt[i])le++;
			su += cnt[i];
		}
	}
	else{
		for(int i = l;i <= bel[l] * block;++i){
			if(cnt[i])le++;
			su += cnt[i];
		}
		for(int i = (bel[r] - 1) * block + 1;i <= r;++i){
			if(cnt[i])le++;
			su += cnt[i];
		}
		for(int i = bel[l] + 1;i <= bel[r] - 1;++i){
			le += jl1[i];
			su += jl2[i];
		}
	}
	return Q(su,le);//以结构体类型返回,方便找答案(不用写两个查询)
}

int main(){
	n = read(),m = read();
	block = sqrt(n);
	int mx = 0;
	for (register int i = 1; i <= n; i++) a[i] = read(),bel[i] = (i - 1) / block + 1;
	for (register int i = 1; i <= m; i++) {
		register int l = read(),r = read(),a = read(),b = read();
		mx = max(mx,b);//这里要取max!!!!!
		q[i] = Node(l,r,a,b,i);
	}
	sort(q+1,q+m+1);
	block = sqrt(mx);
	for(int i = 1;i <= mx;++i){
		bel[i] = (i - 1) / block + 1;
	}
	int l = 1,r = 0;
	for (register int i = 1; i <= m; i++) {//以下是莫队基操
		int ql = q[i].l,qr = q[i].r,qa = q[i].a,qb = q[i].b;
		while(l < ql)Del(l++);
		while(l > ql)Add(--l);
		while(r < qr)Add(++r);
		while(r > qr)Del(r--);
		Q Ans = query(qa,qb);//查询值域内的答案
		ans1[q[i].id] = Ans.sum;
		ans2[q[i].id] = Ans.lei;
	}
	for (int i = 1; i <= m; i++) {//输出答案
		printf("%d %d
",ans1[i],ans2[i]);
	}
	return 0;
}

(Never Give Up)

原文地址:https://www.cnblogs.com/Vocanda/p/13831987.html