hdu 1542 线段树+扫描线 学习

时间:2022-08-29 21:25:38

学习扫描线ing...

玄学的东西...

扫描线其实就是用一条假想的线去扫描一堆矩形,借以求出他们的面积或周长(这一篇是面积,下一篇是周长)

扫描线求面积的主要思想就是对一个二维的矩形的某一维上建立一棵线段树,然后把另一维按高度排序,从下向上枚举即可。

主题思想其他博客说的很明白了,这里重点记录一下细节问题:

下面认为对横坐标建立线段树扫描纵坐标:

首先,由于读入的都是浮点数,所以我们需要对这个东西离散化,具体做法是先去重再进行二分查找,以下标代替浮点数值。

其次,由于普通线段树维护的是一个散点区间,而这里我们需要维护一整个连续的区间,所以区间的端点就是有说道的。具体来讲,我们采用一个“左闭右开”的区间,如何维护?

int lc=findf(p[i].lp);
int rc=findf(p[i].rp)-1;

如上代码所示,我们在查找左端点时是正常查找下标,而查找右端点时我们把查找出来的下标-1,这是为什么呢?

因为我们在线段树上的查询(同时修改)是这样进行的:

void change(int rt,int l,int r)
{
if(tree[rt].lazy)
{
tree[rt].sum=x[r+1]-x[l];
}else if(l==r)
{
tree[rt].sum=0;
}else
{
tree[rt].sum=tree[rt1].sum+tree[rt2].sum;
}
}
void ins(int rt,int l,int r,int v)
{
if(ls>=l&&rs<=r)
{
tree[rt].lazy+=v;
change(rt,ls,rs);
return;
}
int mid=(ls+rs)>>1;
if(l<=mid)
{
ins(rt1,l,r,v);
}
if(r>mid)
{
ins(rt2,l,r,v);
}
change(rt,ls,rs);
}

如上,我们举个例子:

(图片粘不上来)

简而言之,就是如果我们有某种状况:矩形覆盖了区间[3,5],那么如果用普通的线段树,我们就会计算区间[3,4]和...[5]?

这样显然是不可以的

所以如上,我们在查询更新的时候,我们把这个区间外的一个点累计进这个区间里,即我们令区间[3,4]累计的是区间[3,5)(左闭右开)的和,这样就得到了优化。

还有一个问题,就是如果我们这样查询,万一有一个区间叫[5,6],按这样操作就变成了查询[5,7),这样显然是错误的。

所以我们在查询的时候,故意把右端点坐标-1,如果想查询区间[5,6],我们把他变成查询区间[5],然后在查询区间[5]的时候根据上述操作去查询区间[5,6),这样就能获得最好的效果了。

贴代码:

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#define rt1 rt<<1
#define rt2 (rt<<1)|1
#define ls tree[rt].lson
#define rs tree[rt].rson
using namespace std;
struct Tree
{
int lson;
int rson;
int lazy;
double sum;
}tree[800005];
int n,cnt;
struct node
{
double lp,rp,hei;
int typ;
}p[200005];
double x[200005];
bool cmp(node a,node b)
{
return a.hei<b.hei;
}
bool cmp1(double a, double b)
{
return a<b;
}
void buildtree(int rt,int l,int r)
{
ls=l;
rs=r;
tree[rt].lazy=0;
tree[rt].sum=0;
if(l==r)
{
return;
}
int mid=(l+r)>>1;
buildtree(rt1,l,mid);
buildtree(rt2,mid+1,r);
}
int findf(double val)
{
int l=1,r=cnt;
while(l<=r)
{
int mid=(l+r)>>1;
if(x[mid]==val)
{
return mid;
}else if(x[mid]>val)
{
r=mid-1;
}else
{
l=mid+1;
}
}
return -1;
}
void change(int rt,int l,int r)
{
if(tree[rt].lazy)
{
tree[rt].sum=x[r]-x[l];
}else if(l==r)
{
tree[rt].sum=0;
}else
{
tree[rt].sum=tree[rt1].sum+tree[rt2].sum;
}
}
void ins(int rt,int l,int r,int v)
{
if(ls>=l&&rs<=r)
{
tree[rt].lazy+=v;
change(rt,ls,rs);
return;
}
int mid=(ls+rs)>>1;
if(l<=mid)
{
ins(rt1,l,r,v);
}
if(r>mid)
{
ins(rt2,l,r,v);
}
change(rt,ls,rs);
}
int main()
{
int cas=1;
while(1)
{
scanf("%d",&n);
if(n==0)
{
break;
}
int tot=0;
for(int i=1;i<=n;i++)
{
double a,b,c,d;//x1,y1,x2,y2,左上角和右下角
scanf("%lf%lf%lf%lf",&a,&b,&c,&d);
x[++tot]=a;
p[tot].lp=a;
p[tot].rp=c;
p[tot].typ=1;
p[tot].hei=b;
x[++tot]=c;
p[tot].lp=a;
p[tot].rp=c;
p[tot].hei=d;
p[tot].typ=-1;
}
sort(x+1,x+tot+1,cmp1);
sort(p+1,p+tot+1,cmp);
cnt=0;
for(int i=2;i<=tot+1;i++)
{
if(x[i]!=x[i-1])
{
x[++cnt]=x[i-1];
}
}
buildtree(1,1,cnt);
double ret=0;
for(int i=1;i<tot;i++)
{
int lc=findf(p[i].lp);
int rc=findf(p[i].rp);
if(lc<=rc)
{
ins(1,lc,rc,p[i].typ);
}
ret+=tree[1].sum*(p[i+1].hei-p[i].hei);
}
printf("Test case #%d\nTotal explored area: %.2lf\n\n",cas++,ret);
}
return 0;
}