【计算几何】扫描线+线段树

知识点

OI-Wiki 扫描线讲解


一 . 矩形面积并

解释名词:矩形面积并:多个矩形互相有重叠部分,求所有矩形总的覆盖平面的面积。

例题: 洛谷 P5490 【模板】扫描线

题目描述

求 n 个四边平行于坐标轴的矩形的面积并。

输入格式

第一行一个正整数 n。

接下来 n 行每行四个非负整数 x_1, y_1, x_2, y_2,表示一个矩形的四个端点坐标为 (x_1, y_1),(x_1, y_2),(x_2, y_2),(x_2, y_1))。

输出格式

一行一个正整数,表示 n 个矩形的并集覆盖的总面积。

输入输出样例

输入 #1

2
100 100 200 200
150 150 250 255

输出 #1

18000

说明/提示

对于 20% 的数据,1 \leqslant n \leqslant 1000
对于 100% 的数据,1 \leqslant n \leqslant {10}^50 \leqslant x_1 < x_2 \leqslant {10}^90 \leqslant y_1 < y_2 \leqslant {10}^9

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long 
#define lson num<<1
#define rson num<<1|1
using namespace std;
ll int n;
const int maxn=1e6+5;
struct Line{
	ll int from,to,check,mark;
}line[maxn<<1];
struct node{
	ll int l,r,mark,len;
}tree[maxn<<2];
ll int x[maxn],cnt=0;

inline bool cmp(Line x,Line y)
{
	return x.check<y.check;
}

inline void push(ll int num)
{
	if(tree[num].mark) tree[num].len=x[tree[num].r+1]-x[tree[num].l]; 
        //当前位置处于矩形下界时,记录当前线段的长度
	else if(tree[num].l == tree[num].r) tree[num].len=0; //两点重合,说明是同一点距离为0
	else tree[num].len=tree[lson].len+tree[rson].len; //父节点继承左右节点的长度
}

inline void build(ll int num,ll int l,ll int r) //建树
{
	tree[num].l=l; tree[num].r=r;
	if(l == r) return;
	ll int mid=(l+r)>>1;
	build(lson,l,mid);
	build(rson,mid+1,r);	
	return;
}

inline void update(ll int num,ll int l,ll int r,ll int k)
{
	if(x[tree[num].l]>=r || x[tree[num].r+1]<=l) return;
    //  这里加等号的原因:
/*假设现在考虑[1,5],[5,8]两条线段,要修改[1,5]区间的mark,虽然5在这个区间内,但实际的区间应理解成左闭右开,[5,8]并不是我们希望修改的线段,加上等号进行判断*/

	if(x[tree[num].l]>=l && x[tree[num].r+1]<=r) //注意这里输入的数已经为离散化前的数据
	{
		tree[num].mark+=k; //记录当前所在的线段的标记
		push(num);
		return;
	}
	update(lson,l,r,k); //更新左右线段
	update(rson,l,r,k);
	push(num);
}

int main()
{
	scanf("%lld",&n);
	for(int i=1;i<=n;++i)
	{
		ll int x1,y1,x2,y2;
		scanf("%lld %lld %lld %lld",&x1,&y1,&x2,&y2);
		x[(i<<1)-1]=x1; x[i<<1]=x2; 
    //离散化数组,用x[]存点的位置,将这一线段缩成左右两个相互挨着的点
		line[(i<<1)-1]=(Line){x1,x2,y1,1};
		line[i<<1]=(Line){x1,x2,y2,-1};
        //记录矩形的下界限与上界限,上界记为-1,下界记为1;
	}
	n=n<<1;
	sort(line+1,line+n+1,cmp);
	sort(x+1,x+n+1); //数据从小到大排序
	ll int tot=unique(x+1,x+n+1)-x-1; //排好序后,相邻且相同的数进行去重
	build(1,1,tot-1);
	ll int res=0;
	for(int i=1;i<n;++i)
	{
		update(1,line[i].from,line[i].to,line[i].mark);
		res+=tree[1].len*(line[i+1].check-line[i].check);
        //记录矩形面积
	}
	printf("%lld",res);
	return 0;
}

二 . 矩形周长并

例题: 洛谷 P1856 [IOI1998] [USACO5.5] 矩形周长Picture

题目背景

墙上贴着许多形状相同的海报、照片。它们的边都是水平和垂直的。每个矩形图片可能部分或全部的覆盖了其他图片。所有矩形合并后的边长称为周长。

题目描述

编写一个程序计算周长。

 如图1所示7个矩形。

 如图2所示,所有矩形的边界。所有矩形顶点的坐标都是整数。

输入格式

输入文件的第一行是一个整数N(1<=N<5000),表示有多少个矩形。接下来N行给出了每一个矩形左下角坐标和右上角坐标(所有坐标的数值范围都在-10000到10000之间)。

输出格式

输出文件只有一个正整数,表示所有矩形的周长。

输入输出样例

输入 #1

7
-15 0 5 10
-5 8 20 25
15 -4 24 14
0 -6 16 4
2 15 10 22
30 10 36 20
34 0 40 16

输出 #1

228

思路:与用扫描线求矩形并的面积方法相同,建立两棵线段树,一棵用于统计横的线段总长,一棵用于统计竖的线段总长,注意最后处理线段的长度即可。其他与统计矩形面积并的方式一样。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define ll long long
#define lson num<<1
#define rson num<<1|1
using namespace std;
int n;
const int maxn=1e6+5;
int x[maxn<<1],y[maxn<<1];
struct node{
	int l,r,sum,len;
}tree[maxn<<2];
struct Line{
	int l,r,check,mark;
}line_x[maxn<<2];

struct Line2{
	int l,r,check,mark;
}line_y[maxn<<2];
struct node2{
	int l,r,sum,len;
}tree2[maxn<<2];

inline int read()
{
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9')
	{
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9')
	{
		x=(x<<1)+(x<<3)+(c^48);
		c=getchar();
	}
	return x*f;	
}

inline bool cmp(Line x,Line y)
{
	if(x.check == y.check) return x.mark>y.mark;
	else return x.check<y.check;
}

inline bool cmp2(Line2 x,Line2 y)
{
	if(x.check== y.check) return x.mark>y.mark;
	return x.check<y.check;
}

inline void push(int num)
{
	if(tree[num].sum) tree[num].len=x[tree[num].r+1]-x[tree[num].l];
	else if(tree[num].l==tree[num].r) tree[num].len=0;
	else tree[num].len=tree[lson].len+tree[rson].len;
}

inline void push2(int num)
{
	if(tree2[num].sum) tree2[num].len=y[tree2[num].r+1]-y[tree2[num].l];
	else if(tree2[num].l==tree2[num].r) tree2[num].len=0;
	else tree2[num].len=tree2[lson].len+tree2[rson].len;
}

inline void build(int num,int l,int r)
{
	tree[num].l=l; tree[num].r=r;
	tree[num].len=tree[num].sum=0;
	if(l == r) return;
	int mid=(l+r)>>1;
	build(lson,l,mid);
	build(rson,mid+1,r);
}

inline void build2(int num,int l,int r)
{
	tree2[num].l=l; tree2[num].r=r;
	tree2[num].len=tree2[num].sum=0;
	if(l == r) return;
	int mid=(l+r)>>1;
	build2(lson,l,mid);
	build2(rson,mid+1,r);
}

inline void update(int num,int l,int r,int k)
{
	if(x[tree[num].l]>=r||x[tree[num].r+1]<=l) return;
	if(x[tree[num].l]>=l&&x[tree[num].r+1]<=r)
	{
		tree[num].sum+=k;
		push(num);
		return;
	}
	update(lson,l,r,k);
	update(rson,l,r,k);
	push(num);
}

inline void update2(int num,int l,int r,int k)
{
	if(y[tree2[num].l]>=r||y[tree2[num].r+1]<=l) return;
	if(y[tree2[num].l]>=l&&y[tree2[num].r+1]<=r)
	{
		tree2[num].sum+=k;
		push2(num);
		return;
	}
	update2(lson,l,r,k);
	update2(rson,l,r,k);
	push2(num);
}

int main()
{
	n=read();
	for(int i=1;i<=n;++i)
	{
		int x1=read(),y1=read(),x2=read(),y2=read();
		x[(i<<1)-1]=x1; x[i<<1]=x2;
		y[(i<<1)-1]=y1; y[i<<1]=y2;
		line_x[(i<<1)-1]=(Line){x1,x2,y1,1};
		line_x[(i<<1)]=(Line){x1,x2,y2,-1};
		line_y[(i<<1)-1]=(Line2){y1,y2,x1,1};
		line_y[(i<<1)]=(Line2){y1,y2,x2,-1};
	}
	n<<=1;
	sort(x+1,x+n+1);
	sort(y+1,y+n+1);
	sort(line_x+1,line_x+n+1,cmp);
	sort(line_y+1,line_y+n+1,cmp2);
	int tot1=unique(x+1,x+n+1)-x-1;
	int tot2=unique(y+1,y+n+1)-y-1;
	build(1,1,tot1-1); build2(1,1,tot2-1);
	ll int res=0,temp1=0,temp2=0;
	for(int i=1;i<=n;++i)
	{
		update(1,line_x[i].l,line_x[i].r,line_x[i].mark);
		update2(1,line_y[i].l,line_y[i].r,line_y[i].mark);
		res+=abs(tree[1].len-temp1)+abs(tree2[1].len-temp2); 
     //注意这里统计线段长度,用当前扫到的值减去上一次已有的值,得到此次扫描增加的线段长度
		temp1=tree[1].len; temp2=tree2[1].len;
	}
	printf("%lld",res);
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值