HDU 4417 —— Super Mario(树状数组,离散化,离线处理)

题目:http://acm.hdu.edu.cn/showproblem.php?pid=4417

意思比较简单,就是给N个数(下标从0开始),然后q个询问,三个参数,L,R,H,询问序列中在【L,R】这个区间上小于等于H的个数。

挺综合的一道题目。因为数字最多100000个,数值最多达10^9,所以首先要对数值进行离散化。

像这种区间查询问题,用S(X,H)表示从0开始到X,小于等于H的个数,那么每个查询就可以转化成S(R,H)-S(L-1,H),这个直接写成树状数组或线段树是比较困难的。

转成离线处理会比较简单。具体来说就是把每个查询拆成两个事件,一个对应L,一个对应R,一共有2q个事件,将所有事件从左到右排序,如果对应的位置相同,那么对应左端点的应该优先在前。

然后就是遍历0~N-1,遇到事件左端点的,用k表示H在离散化之后对应的下标,减掉SUM(k),把a[i]对应的位置添加到树状数组中,遇到右端点,就加上SUM(k)。

这样,由于到了某个i的时候,后面的数值还没加进来,所以对当前值没有影响,求出来的SUM自然就是对应0到当前端点的值。

最后在一口气将所有答案输出即可。

具体还是见代码吧。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 100000
#define M 200010
struct Event{
	int type;//事件类型,0表示左端点,1表示右端点
	int id;//事件对应的查询的编号
	int x;//事件对应的端点
	int v;//事件对应的H值
	bool operator < (const Event& tmp)const{
		if(x==tmp.x)	return type<tmp.type;
		return x<tmp.x;
	}
}ev[M];
inline void in(int& num){
	char c=getchar();
	num=0;
	while(c<48 || c>57)	c=getchar();
	while(c>=48 && c<=57){
		num = num*10+c-48;
		c = getchar();
	}
}
int t, ct, n, m, q, p, l, r, x, i, j, k;
int a[N], b[M], s[M], ans[N];
void add(int v){
	for(;v<=m;v+=(v&(-v)))	s[v]++;
}
int sum(int v){
	int res=0;
	for(;v;v-=(v&(-v)))	res+=s[v];
	return res;
}
int main(){
	in(t);
	for(ct=1; ct<=t; ct++){
		in(n); in(q);
		m=0;
		for(i=0; i<n; i++){
			in(a[i]);
			b[m++]=a[i];
		}
		for(i=0; i<q; i++){
			in(l); in(r); in(x);
			ev[i<<1].type=0; ev[i<<1].id=i; ev[i<<1].x=l; ev[i<<1].v=x;
			j = (i<<1)|1;
			ev[j].type=1; ev[j].id=i; ev[j].x=r; ev[j].v=x;
			b[m++] = x;
		}
		p = q<<1;
		sort(ev, ev+p);
		sort(b, b+m);
		m = unique(b, b+m)-b;
		memset(s,0,sizeof(s));
		j=0;
		for(i=0; i<n; i++){
			//先处理掉左端点
			while(j<p && ev[j].type==0 && ev[j].x==i){
				k = lower_bound(b, b+m, ev[j].v)-b+1;
				ans[ev[j].id] = 0 - sum(k);
				j++;
			}
			//更新当前的a[i]
			k = lower_bound(b, b+m, a[i])-b+1;
			add(k);
			//处理右端点
			while(j<p && ev[j].type==1 && ev[j].x==i){
				k = lower_bound(b, b+m, ev[j].v)-b+1;
				ans[ev[j].id] += sum(k);
				j++;
			}
		}
		printf("Case %d:\n", ct);
		for(i=0; i<q; i++)	printf("%d\n", ans[i]);
	}
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值