作者:henu_wxj
链接:https://blog.nowcoder.net/n/3649794324b14ec2a1870ba9ef0868d1
来源:牛客网
久闻扫描线算法的大名,趁着最近学习计算几何,学习一波扫描线。
扫描线就是将一个图形进行扫描,从上到下||从左到右。一段一段的扫描,每扫描到一个位置,刷新一下记录的属性。
直接说可能比较抽象,用一张图片来理解可能会更好:
如图所示,我们有三个矩形(红,绿,蓝)我们如何对他进行扫描呢?有两种方法,分别是:1.横向扫描,2.纵向扫描。他们的实现方法都类似,所以我们就用其中的一种扫描法来举例子:
我们使用,默认从左到右的横向扫描法。但是如果一个单位一个单位的扫描的话,不仅可能花时间,精确度也无法保证,所以我们可以对所要扫描的图形进行处理,得到一些断点(线)只进行对这些线的扫描,因为这些线之外的地方与这些线是相同的,这样可以减少很多的工作量。再次那上图举例子吧。
首先我们从这三个图形中得到6个断点(棕色的线)。
意思是我们只用进行六次扫描即可。
对于每次扫描,我们可以根据扫描到的结果,对y轴上的投影进行更新。如图所示:
我们每次只用计算,y轴上的有效面积*这一段的长度,即可得出这一段的面积。
因为这一段的长度很容易就可以求出来,因此我们的任务就是对每次扫描的线段进行y轴上的维护。使用线段树进行维护,我们可以将线段树横着放,就是对y轴的维护了:
每个节点记录一个区间的覆盖情况,这样就可以快速的维护映射在y轴上的投影了。
代码就非常好实现了,线段树的板子加上扫描新,改一改就是了,由于我们用点表示区间,因此要注意一个节点代表的区间上下界,因此向下探寻的时候要注意不要探错了。
// luogu-judger-enable-o2
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<map>
#include<set>
#include<deque>
#include<queue>
#include<stack>
#include<bitset>
#include<string>
#include<fstream>
#include<iostream>
#include<algorithm>
using namespace std;
#define LL long long
#define Pair pair<int,int>
//#define max(a,b) (a)>(b)?(a):(b)
//#define min(a,b) (a)<(b)?(a):(b)
#define clean(a,b) memset(a,b,sizeof(a))// 水印
//std::ios::sync_with_stdio(false);
// register
const int MAXN=1e3+10;
const int INF32=0x3f3f3f3f;
const LL INF64=0x3f3f3f3f3f3f3f3f;
const int MOD=1e9+7;
const double EPS=1.0e-12;
const double PI=acos(-1.0);
struct Line{
double x1,y1,y2;int flag;//falg记录该线段是应该添加还是消除
Line(double _x1=0,double _y1=0,double _y2=0,int _flag=0){
x1=_x1;y1=_y1;y2=_y2;flag=_flag;
}
friend int operator < (Line a,Line b){
return a.x1<b.x1;
}
};
struct Node{
double len,len2;int l,r,flag;//flag记录该范围有没有被完全覆盖
Node(double _len=0,double _len2=0,int _l=0,int _r=0,int _flag=0){
len=_len;len2=_len2;l=_l;r=_r;flag=_flag;
}
};
Line line[MAXN<<2];
Node tree[MAXN<<3];
double y[MAXN<<2];
int n;
int Location_x(double key,int l,int r){
while(l<=r){
int mid=(l+r)>>1;
if(fabs(y[mid]-key)<EPS) return mid;
else if(y[mid]>key) r=mid-1;
else l=mid+1;
}return -1;
}
void Build(int l,int r,int rt){
tree[rt]=Node(0,0,l,r,0);
if(l+1==r) return ;
int mid=(l+r)>>1;
Build(l,mid,rt<<1);Build(mid,r,rt<<1|1);
}
void PushUp(int rt){
if(tree[rt].flag) tree[rt].len=y[tree[rt].r]-y[tree[rt].l];
else if(tree[rt].l+1==tree[rt].r) tree[rt].len=0;
else tree[rt].len=tree[rt<<1].len+tree[rt<<1|1].len;
}
//void PushDown(int rt){
// if(tree[rt].flag==1){
//
// }
//}
void Update(int ql,int qr,int v,int rt){
// PushDown(rt);
if(tree[rt].l==ql&&tree[rt].r==qr){
tree[rt].flag+=v;
PushUp(rt);return ;
}
if(qr<=tree[rt<<1].r) Update(ql,qr,v,rt<<1);
else if(ql>=tree[rt<<1|1].l) Update(ql,qr,v,rt<<1|1);
else{
Update(ql,tree[rt<<1].r,v,rt<<1);
Update(tree[rt<<1|1].l,qr,v,rt<<1|1);
}PushUp(rt);
}
int main(){
while(~scanf("%d",&n)){
double x1,x2,y1,y2;
for(int i=1;i<=n;++i){
scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
line[i]=Line(x1,y1,y2,1);//+1代表增加一条边 增加这条边所对应的的长度
line[i+n]=Line(x2,y1,y2,-1);//-1代表消去这条边 ,将这条边对线段树的影响消除
y[i]=y1;y[i+n]=y2;
}n*=2;
sort(y+1,y+1+n);sort(line+1,line+1+n);
int temp=2;//将离散的y轴进行去重
for(int i=2;i<=n;++i){
if(fabs(y[i]-y[i-1])>EPS) y[temp++]=y[i];
}--temp;
Build(1,temp,1);//初始化线段树
double ans=0;int l,r;
for(int i=1;i<n;++i){
//找到一个更新的范围
l=Location_x(line[i].y1,1,temp);r=Location_x(line[i].y2,1,temp);
Update(l,r,line[i].flag,1);//对线段树进行更新
// cout<<"Update l,r,y[l],y[r],len2 :"<<l<<" "<<r<<" "<<y[l]<<" "<<y[r]<<" "<<tree[1].len2<<endl;
ans+=tree[1].len2*(line[i+1].x1-line[i].x1);
}printf("the sum area are : %lfn",ans);
}
}
/*
2
5
1 1 4 2
1 3 3 7
2 1.5 5 4.5
3.5 1.25 7.5 4
6 3 10 7
3
0 0 1 1
1 0 2 1
2 0 3 1
*/
下面,我们来用一些题目来测试一下我们的板子:这是关于扫描线的习题汇总
https://blog.csdn.net/qq_40482358/article/details/88425330
查看作者更多博客:https://blog.nowcoder.net/remil