扫描线算法——计算几何问题求解

——引例

有时候我们会碰见一些求n个矩形的面积并(就是说n个矩形,每两个矩形之间都有可能重叠,然后问你这n个矩形的总占地面积为多少),或者周长并(就是说n个矩形拼成的最外围轮廓的周长和)

对于这种问题,在最初的做题中用的都是线段树+离散化去维护区间去求解最后的面积并或者周长并,但是在后面就有了本篇讲述的扫描线算法

不论是线段树+离散化,还是扫描线算法,其算法的时间复杂度都为O(N*logN) 

扫描线算法主要倾向线段树+离散化这类思想,然后扫描求解过程有点像积分,那么我们开始讲解扫描线。 

 问题+解决思路

P5490 【模板】扫描线

 

这题其实我当时没写,我写的是另一道,但是由于都是一个板子改一下数据即可,那么现在开始来进行扫描线算法的分析和应用

分析扫描线

扫描线:假设有一条扫描线从一个图形的下方扫向上方(或者左方扫到右方),那么通过分析扫描线被图形截得的线段就能获得所要的结果。该过程可以用线段树进行加速。

对于1的图就是这样,每次扫描线只有碰到矩形的边界才会停止,恰好将图形分成2*n-1个区域

 对于2来说,很显而易见,如果我们矩形的上下边界高度存储起来的话,那么我们每个区块的高度,其实就是y[i+1]-y[i]

 对于3来说,区块的长度就是矩形那条线的最长的长度,首先就是对于高度是1的长度为(20~80),对于3高度的长度(10~80),对于5和6的长度,很明显的都是(10~90)

对于4来说,当前区块的面积并就为高度*当前区块的长度

解释一下5,区块的长度可以用线段树来维护, 每当标记结点cnt!=0的时候,说明被覆盖了,直接加上区间值即可,别的都是左子树长度+右子树长度

细节

计算时右坐标右偏移一位,传参时右坐标左偏移一位 

我们现在开始来说明为什么会有这样的结论

我们先来写出X坐标离散化后的一个表

按照我们上面这个图我们可以建出一棵树

 结合上面那个表,我们发现如果真的按照上面的表来算横坐标的话,那么对于4,5这两个节点,一个是10~20,另一个是30~30这样很明显是不对的中间有长达10的空挡,并且我们的线段树节点要保存的不是一个点,而是一条线段,所以我们的右端点在计算的时候要进行右偏移一位

就好比我们有一个扫描线,扫描到的是10~50,我们要存储1~4吗?不是的,因为我们在计算的时候右端点会向右偏移一位,所以我们此时在存储右端点的时候要将右端点向左偏移一位

分析所需要用到的函数和变量 

扫描线的结构体 :

struct line{
	int x1,x2;//扫描线的左边界和右边界
	int y;//扫描线所处的高度,也就是y
	int tag;//判断是下边还是上边,下边+贡献,下边-贡献
}L[10086];

线段树节点的结构体:

struct node{
	int l;//表示节点覆盖的左端点
	int r;//节点覆盖的右端点
	int len;//表示长度
	int cnt;//表示是否被覆盖的标志 
}tree[10086];

建树的函数:

void build(int i,int l,int r)
{
	tree[i].l=l,tree[i].r=r;
	if(l==r)
	{
		return;
	}
	int mid=(l+r)/2;
	build(i*2,l,mid);
	build(i*2+1,mid+1,r);
	return ;
}

回退计算最终覆盖长度的函数

void push_up(int i)
{
	if(tree[i].cnt!=0)//有覆盖标记直接计算
	{
		tree[i].len=X[tree[i].r+1]-X[tree[i].l];
	}
	else//没有覆盖标记计算左子树和右子树的结点之和
	{
		tree[i].len=tree[i*2].len+tree[i*2+1].len;
	}
	return ;
}

扫描线扫描过程中遇到的边界的线段和

void add(int i,int l,int r,int tag)
{
	if(l>tree[i].r||tree[i].l>r)
	{
		return ;
	}
	if(tree[i].l>=l&&tree[i].r<=r)
	{
		tree[i].cnt+=tag;
		push_up(i);
		return ;
	}
	add(i*2,l,r,tag);
	add(i*2+1,l,r,tag);
	push_up(i);
	return ;
}

扫描线板子完整代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
 
int n;
int a,b,c,d;
struct line{
	int x1,x2;
	int y;
	int tag;
}L[10086];

struct node{
	int l;
	int r;
	int len;//表示长度
	int cnt;//表示是否被覆盖的标志 
}tree[10086];
int X[10086];

bool cmp(line x,line y)
{
	return x.y<y.y;
}

void push_up(int i)
{
	if(tree[i].cnt!=0)
	{
		tree[i].len=X[tree[i].r+1]-X[tree[i].l];
	}
	else
	{
		tree[i].len=tree[i*2].len+tree[i*2+1].len;
	}
	return ;
}

void build(int i,int l,int r)
{
	tree[i].l=l,tree[i].r=r;
	if(l==r)
	{
		return;
	}
	int mid=(l+r)/2;
	build(i*2,l,mid);
	build(i*2+1,mid+1,r);
	return ;
}

void add(int i,int l,int r,int tag)
{
	if(l>tree[i].r||tree[i].l>r)
	{
		return ;
	}
	if(tree[i].l>=l&&tree[i].r<=r)
	{
		tree[i].cnt+=tag;
		push_up(i);
		return ;
	}
	add(i*2,l,r,tag);
	add(i*2+1,l,r,tag);
	push_up(i);
	return ;
}

signed main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a>>b>>c>>d;
		L[i]={a,c,b,-1};
		L[i+n]={a,c,d,1};
		X[i]=a;
		X[i+n]=c;
	}
	sort(L+1,L+1+2*n,cmp);
	sort(X+1,X+1+2*n);
	int len=unique(X+1,X+1+2*n)-X-1;
	build(1,1,len-1);
	int ans=0;
	for(int i=1;i<2*n;i++)
	{
		int l=lower_bound(X+1,X+1+len,L[i].x1)-X;
		int r=lower_bound(X+1,X+1+len,L[i].x2)-X;
		add(1,l,r-1,L[i].tag);
		ans+=tree[1].len*(L[i+1].y-L[i].y);
	}
	cout<<ans;
	return 0;
}

例题:

由于这种问题都是一种板子,所以下面的例题也就不放代码了,当然了只是对于面积并来说,还有一种周长并,日后更新切莫着急

P5490 【模板】扫描线

P1884 [USACO12FEB] Overplanting S

我感觉俩题都没啥区别,但是上面是个蓝,下面是个绿,没看懂。

各位看官老爷再见

相关推荐

  1. 基于萤火虫算法求解订单分批问题

    2024-07-20 18:06:03       66 阅读
  2. 54 回溯算法求解全排列问题

    2024-07-20 18:06:03       66 阅读
  3. 二次分配问题(遗传算法求解

    2024-07-20 18:06:03       67 阅读
  4. 算法设计和分析1( 算法问题求解基础)

    2024-07-20 18:06:03       46 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-20 18:06:03       171 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-20 18:06:03       189 阅读
  3. 在Django里面运行非项目文件

    2024-07-20 18:06:03       157 阅读
  4. Python语言-面向对象

    2024-07-20 18:06:03       170 阅读

热门阅读

  1. B端产品方向(五)

    2024-07-20 18:06:03       30 阅读
  2. Canvas总结二

    2024-07-20 18:06:03       36 阅读
  3. 二叉树---从中序与后序遍历序列构造二叉树

    2024-07-20 18:06:03       33 阅读
  4. 冒泡排序代码

    2024-07-20 18:06:03       33 阅读
  5. qt log 输出为文件,每分钟换一个log文件

    2024-07-20 18:06:03       30 阅读
  6. Docker 运维常用命令及问题案例

    2024-07-20 18:06:03       28 阅读
  7. 从零开始!Jupyter Notebook的安装教程

    2024-07-20 18:06:03       33 阅读
  8. HttpHeaders类详解,这一篇就够了

    2024-07-20 18:06:03       40 阅读
  9. WPF中UI元素继承关系

    2024-07-20 18:06:03       33 阅读
  10. Linux复习01

    2024-07-20 18:06:03       30 阅读
  11. 算法刷题笔记 八数码(C++实现)

    2024-07-20 18:06:03       38 阅读
  12. Apollo开发指南

    2024-07-20 18:06:03       32 阅读