扫描线 算法学习笔记

扫描线

一般如果是从左往右扫的话,首先用结构体表示图形(长方形或正方形)的左右两边;

struct D{
	int f,X,Y1,Y2;
}du[N*2];

f 表示该边时入边还是出边,入边为 1 , 出边表示 -1 ;

X 表示该边的x坐标,Y1 表示该边的上边y坐标,Y2 表示该边的下边y坐标;

如图:
在这里插入图片描述

这个还没完,可以发现Y的取值范围为1-1e9,因为我们线段树维护的是Y(这个后面讲),所以肯定爆空间,图形数量是1e5,所以离散化Y是必须的一步;

Y离散化完了,就再把结构体du[]以X由小到大排序,然后从左往右扫;

再说线段树维护什么;

struct Node{
	int l,r,len,lz;
}tr[N*8];

l,r意义不变,len表示的就是该区间的长度,lz表示该区间被加入的次数(也就是上面的入边和出边 f );

最最最重要也最难的部分来了

void pp(int k){
	if(tr[k].lz) tr[k].len=val[tr[k].r+1]-val[tr[k].l];
	else if(tr[k].l==tr[k].r) tr[k].len=0;
	else tr[k].len=tr[ls].len+tr[rs].len;
}

记住,扫描线区间修改操作不需要往下转标记,也就说不需要每个叶子结点的值都改变;

所以只要pp,向上传就可以;

当lz有值时,就是说该区间有边加入时,那么该区间的长度len直接等于r-l(注意l,r以被离散化,val表示离散化前的原值),这里还有个r+1,也是非常非常重要难理解的一部分,之后再讲;

当lz没有值时,只要加上左右区间的长度即可;

我们查询的时候,一般是直接查全局的长度,tr[1].len;

所以最后我们只要区间修改,和直接查询即可;

LL ans=0;
	for(int i=1;i<tot;i++){
		update(du[i].Y1,du[i].Y2-1,du[i].f,1);
		ans+=1LL*tr[1].len*(du[i+1].X-du[i].X);
	}

修改的值就是du[i].f,表示该边是加入还是出去,这里为啥是Y2-1,这跟前面为啥是r+1有联系;

现在讲前面最重要,最难理解的黄色部分:一切的一切都是离散化导致的

假设,l=1,r=m,我们要在区间[1,m]中加1,是不是直接len=len[d]-len[l]+len[r]-len[d+1];

这样算出来的len是错的,为啥?因为d–d+1之间的距离我们没算,这也是线段树维护的Y的距离而不是传统的线段树的区别了;

全部代码来自这道题:

洛谷·P5490 【模板】扫描线

代码:

#include<bits/stdc++.h>
#define LL unsigned long long
#define pa pair<int,int>
#define ls k<<1
#define rs k<<1|1
#define inf 0x3f3f3f3f
using namespace std;
const int N=100010;
const int M=50100;
const LL mod=10007;
int n,x_1[N],x_2[N],y_1[N],y_2[N],a[N*2],cnt,tot,val[N*2];
struct D{
	int f,X,Y1,Y2;
}du[N*2];
struct Node{
	int l,r,len,lz;
}tr[N*8];
void build(int l,int r,int k){
	tr[k].l=l,tr[k].r=r;
	if(l==r) return;
	int d=(l+r)>>1;
	build(l,d,ls);
	build(d+1,r,rs);
}
void pp(int k){
	if(tr[k].lz) tr[k].len=val[tr[k].r+1]-val[tr[k].l];
	else if(tr[k].l==tr[k].r) tr[k].len=0;
	else tr[k].len=tr[ls].len+tr[rs].len;
}
void update(int l,int r,int w,int k){
	if(tr[k].l>=l&&tr[k].r<=r){
		tr[k].lz+=w;
		pp(k);
		return;
	}
	int d=(tr[k].l+tr[k].r)>>1;
	if(l<=d) update(l,r,w,ls);
	if(r>d) update(l,r,w,rs);
	pp(k);
}
bool cmp(D p,D q){
	if(p.X==q.X) return p.f>q.f;
	return p.X<q.X;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d%d%d%d",&x_1[i],&y_1[i],&x_2[i],&y_2[i]);
		a[++cnt]=y_1[i],a[++cnt]=y_2[i];
	}
	sort(a+1,a+cnt+1);
	cnt=unique(a+1,a+cnt+1)-a-1;
	for(int i=1;i<=n;i++){
		int p1=lower_bound(a+1,a+cnt+1,y_1[i])-a;
		int p2=lower_bound(a+1,a+cnt+1,y_2[i])-a;
		val[p1]=y_1[i],val[p2]=y_2[i];
		du[++tot].f=1,du[tot].X=x_1[i],du[tot].Y1=p1,du[tot].Y2=p2;
		du[++tot].f=-1,du[tot].X=x_2[i],du[tot].Y1=p1,du[tot].Y2=p2;
	}
	sort(du+1,du+tot+1,cmp); 
	build(1,cnt-1,1);
	LL ans=0;
	for(int i=1;i<tot;i++){
		update(du[i].Y1,du[i].Y2-1,du[i].f,1);
		ans+=1LL*tr[1].len*(du[i+1].X-du[i].X);
	}
	printf("%lld\n",ans);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值