二维数点扫描线做法(SHOI2007园丁的烦恼)

二维数点问题想了好几周了,总算在今天实际手打出来,这里讲一下我的理解。

首先一维的数点很明显可以利用前缀和,或者树状数组就可以做了,二维的话因为多了个维度,所以会变得麻烦一些,总的思路肯定还是降维,处理成一维的情况,下面就以模板题为例。

SHOI2007园丁的烦恼

这题就是纯粹的二维数点,不过因为数据量比较大,需要对所有的点坐标离散化一下。然后我们来考虑对于一个查询的矩形,应该如何操作。我在学习的时候看题解那边给出的做法基本都是将一次查询分裂为四个虚点,分别求出四个虚点到原点的矩形面积,再用类似二位前缀和的容斥思想加减一下得出答案。但是在机房的某位佬教我,二维数点可以用扫描线的做法,只要扫一遍就可以了,不用这么麻烦拆四个点。下面我来着重介绍一下扫描线的做法。

图片有点抽象,大家将就看看,这里面黑色的框是查询的矩形,我们扫描线是与x轴平行的一条线,遍历y轴向上走,从y=1开始,将y=1的点插入树状数组,然后到了某个y有查询,记录下当前查询的l到r范围内树状数组的区间和,再往上接着加点,到了橙色框的上界,又是一次查询,再记录一次l到r的区间和,那么黑色矩形的点数就是橙色框减去绿色框。所以我们只需要将每次的查询分裂为两个查询,记录小查询的左端点右端点, 以及对应的答案下标(也就是离线处理后对应的输出顺序),最重要的是小查询的属性,下面的是减查询,上面的是加查询。

所以显然的,我们要对所有的点和小查询的y坐标升序排序。这里教大家一个偷学到的方法,就是点坐标可以直接用vector[]来存储,那样去遍历vector[y]时,就确保这些点的纵坐标都是y,也就起到了排序的效果。同理小查询也可以用这样的二维数组,第一维直接表示纵坐标。然后经过麻烦的离散化,就可以写出来大体的模型,就会发现样例都过不了。QAQ

然后我去调试了一下,发现问题在于如果点刚好位于矩形的框上时,对于减查询应该要先计算面积,再去插入点,才不会多扣面积,对于加查询,又要先加入点,再去计算面积,才不会漏了点。那么就在扫描线移动的过程中注意一下顺序即可。最好完美ac~,下面是代码。

#include <bits/stdc++.h>
#define pii pair<int,int>
#define ll long long
using namespace std;

//单纯的二维数点扫描线y轴遍历,减查询先算,再加点,再算加查询
//这题主要是离散化太麻烦了,不需要离散化的题会简单些



const int maxn=1e6+10;
int b[5*maxn],cnt=0,ans[maxn],qcnt=0;
int tem;
vector<int>v[maxn];
vector<pii>nodes;
int tree[maxn];
void update(int i,int x){
	for(int pos=i;pos<=tem;pos+=pos&(-pos))
		tree[pos]+=x;
}
int chaxun(int i){
	int ans=0;
	for(int pos=i;pos;pos-=pos&(-pos))
		ans+=tree[pos];
	return ans;
}
inline int read()
{ 
    int x=0,f=0,ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return f?-x:x;
}
struct query{
	int l,r;//l,r表示查询的左端点和右端点
	int y,id;//y表示对应的y坐标,id对应答案的下标
	bool typ;//typ为0表示减查询,typ为1表示加查询
}qtem[maxn];
vector<query>q[maxn];
int main(){
	int n=read(),m=read();
	for(int i=1;i<=n;i++){
		int x=read(),y=read();
		b[++cnt]=x;b[++cnt]=y;
		nodes.push_back(pii(x,y));
	}
	for(int i=1;i<=m;i++){
		int x1=read(),y1=read(),x2=read(),y2=read();
			qtem[++qcnt]={x1,x2,y1,i,0};
			qtem[++qcnt]={x1,x2,y2,i,1};
			b[++cnt]=x1;b[++cnt]=x2;b[++cnt]=y1;b[++cnt]=y2;
	}
	sort(b+1,b+1+cnt);
	tem=unique(b+1,b+1+cnt)-b-1;
	for(auto it:nodes){
		int x=lower_bound(b+1,b+tem+1,it.first)-b;
		int y=lower_bound(b+1,b+tem+1,it.second)-b;
		v[y].push_back(x);//此时xy都已经离散化后
	}
	for(int i=1;i<=qcnt;i++){
		int yy=lower_bound(b+1,b+tem+1,qtem[i].y)-b;
		int lll=lower_bound(b+1,b+tem+1,qtem[i].l)-b;
		int rrr=lower_bound(b+1,b+tem+1,qtem[i].r)-b;
		q[yy].push_back(query{lll,rrr,yy,qtem[i].id,qtem[i].typ});
	}
	for(int i=1;i<=tem;i++){//i开始扫描y轴
		for(auto it:q[i]){
			if(it.typ==0){
				ans[it.id]-=chaxun(it.r)-chaxun(it.l-1);
			}
		}
		for(auto it:v[i]){
			update(it,1);
		}
		for(auto it:q[i]){
			if(it.typ==1){
				ans[it.id]+=chaxun(it.r)-chaxun(it.l-1);
			}
		}
	}
	for(int i=1;i<=m;i++)
		cout<<ans[i]<<endl;
	system("pause");
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值