扫描线用于求若干个相交矩形的面积并,因为用几何方法在相交的情况复杂的时候难以计算。下面给2个矩形的情况做例子
给定2个矩形对角的点坐标,则下图的面积为图2的三种颜色面积和
图1
图2
现在假设有一条竖着的线从左边往右边扫,扫到矩阵的边时,若是入边,将边这一个区间的cover属性+1,出边则-1
图中圈圈的数字是第几条边,花括号的数是cover值。如图扫描线经过②号边(入边)时最上面部分是1,下面还是1,中间是2,到出边就又减了
图3
则扫描到②号线时,计算一次面积,长为②号线的x减去①号线(前面一条线)的x,高为[Ymin,Ymax]中所有cover为正的高度和
(在这里即为①号线的Yup,Ydown差)。这样重复下去即求得面积和。
放线段树上,区间端点为各条边的上下2个y值,每个区间也都有个cover属性表示覆盖的次数,如上所提。
图4
假设点为[1,1],[2,2],[3,3],[4,4](按图1的情况),则有上图。①号线为[1,3],②为[2,4]...
看这图的第二行,前面一个区间是[1,2],后面是[2,4],而不是{3,4],是因为在矩形里,区间是连续的,[2,3]是存在的,如果为mid+1则[2,3]会被弄没。
第三行:[1,2]没有再继续分成[1,1]和[2,2],是因为这样的点区间没有意义,一条线当然要两个点
然而区间端点并不一定是这么好的整数,即可能是浮点数,还可能坐标很大,所以需要对这些区间的Y值离散化(这里不解释,可见),离散化后就跟上图的1,2,3,4一样了。
HDU 1542 Atlantis 也是POJ 1151 Atlantis 不过POJ用G++交的话记得是%.2f而不是%.2lf
思路一:保存区间时区间端点都用double类型,避免离散化的麻烦。然后只对叶区间处理,如扫描图3里的第②条线时,
面积为②号线上cover为1、为2、为1的三段这和。
这种情况下求每一部分面积时:长为当前线的x减去前面一条线的x,代码里的tree[i].x保存上一条线的x值,可以这样做是因为每次求第i条线时,
上一条线已经扫过,在建树时设置x为-1表示没有上一条。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <vector>
#include <iostream>
#include <iomanip>
#include <algorithm>
using namespace std;
#define ll __int64
#define INF 0x7FFFFFFF
#define INT_MIN -(1<<31)
#define eps 10^(-6)
#define Q_CIN ios::sync_with_stdio(false);
#define REP( i , n ) for ( int i = 0 ; i < n ; ++ i )
#define FOR( i , a , b ) for ( int i = a ; i <= b ; ++ i )
#define CLR( a , x ) memset ( a , x , sizeof (a) );
#define RE freopen("1.in","r",stdin);
#define WE freopen("1.out","w",stdout);
#define MOD 10009
#define debug(x) cout<<#x<<":"<<(x)<<endl;
//#define lson i<<1,l,m
//#define rson i<<1|1,m+1,r
const int maxn=105;
const int maxN=100010;
double y[maxn<<1];
struct Line
{
double x;
double y_up,y_down;
int dir; //1为入边,-1为出边
}L[maxn<<1]; //2*n的边数
struct Tree
{
double y_up,y_down,x; //x为上一条线段的x值
int cover;
bool leaf; //是否为叶区间
}tree[maxN<<2];
bool cmp(Line a,Line b)
{
return a.x<b.x;
}
void build(int i,int l,int r)
{
tree[i].x=-1;tree[i].y_up=y[r];tree[i].y_down=y[l];tree[i].cover=0;tree[i].leaf=false;
if(l+1==r) //[1,2]这样的区间就不分下去了
{
tree[i].leaf=true;
return;
}
int m=(l+r)>>1;
build(i<<1,l,m);
build(i<<1|1,m,r); //区间是连续的,所以右区间的l要与左区间的r相同
}
double update(int i,double x,double y_down,double y_up,int dir)
{
if(y_down>=tree[i].y_up||y_up<=tree[i].y_down) //如在[1,2]里查询[3,4]直接返回0
return 0.0;
if(tree[i].leaf) //非叶区间不管
{
if(tree[i].cover>0) //有覆盖到,求面积
{
double xx=tree[i].x;
double ch=(x-xx)*(tree[i].y_up-tree[i].y_down);
tree[i].cover+=dir; //边覆盖次数
tree[i].x=x;
return ch;
}else
{
tree[i].x=x; //设x为x
tree[i].cover+=dir;
return 0.0;
}
}
double s1=update(i<<1,x,y_down,y_up,dir);
double s2=update(i<<1|1,x,y_down,y_up,dir);
return s1+s2;
}
int main()
{
int n;
double x1,x2,y1,y2;
int te=1;
// RE
while(cin>>n,n)
{
int cnt=1;
FOR(i,1,n)
{
cin>>x1>>y1>>x2>>y2;
L[cnt].x=x1;L[cnt].y_down=y1;L[cnt].y_up=y2;L[cnt].dir=1;y[cnt]=y1;cnt++;
L[cnt].x=x2;L[cnt].y_down=y1;L[cnt].y_up=y2;L[cnt].dir=-1;y[cnt]=y2;cnt++;
}
// sort(L+1,L+cnt,cmp);
// sort(y+1,y+cnt);
// build(1,1,cnt-1);
sort(L+1,L+2*n+1,cmp); //线段按x排序
sort(y+1,y+2*n+1); //y数组升序
build(1,1,2*n);
double ans=0.0;
FOR(i,1,2*n)
ans+=update(1,L[i].x,L[i].y_down,L[i].y_up,L[i].dir);
cout<<"Test case #"<<te++<<endl;
cout<<setiosflags(ios::fixed);
cout<<"Total explored area: "<<setprecision(2)<<ans<<endl<<endl;
}
return 0;
}
思路二(主流一点):给每个区间增加一个len属性,表示该区间能与下一条线段计算用的高度,然后顺势每次插入线段就用整个大区间(树)与下一条能算的高度求出面积。
(区间存端点有点离散化的思想,把区间存的都变成了常规的整数l,r)如插入②时,因为有y数组排序,插入的区间就是【2,4】,则【2,4】能与下一条计算的是y[4]-y[2],然后通过pushup把【1,2】能计算的也加进来,就可以求②和③中间的面积了。因为区间的l,r是int,所以插入的线段的区间需要用二分在y数组里找出下标。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <vector>
#include <iostream>
#include <iomanip>
#include <algorithm>
using namespace std;
#define ll __int64
#define INF 0x7FFFFFFF
#define INT_MIN -(1<<31)
#define eps 10^(-6)
#define Q_CIN ios::sync_with_stdio(false);
#define REP( i , n ) for ( int i = 0 ; i < n ; ++ i )
#define FOR( i , a , b ) for ( int i = a ; i <= b ; ++ i )
#define CLR( a , x ) memset ( a , x , sizeof (a) );
#define RE freopen("1.in","r",stdin);
#define WE freopen("1.out","w",stdout);
#define MOD 10009
#define debug(x) cout<<#x<<":"<<(x)<<endl;
//#define lson i<<1,l,m
//#define rson i<<1|1,m+1,r
const int maxn=105;
const int maxN=100010;
double y[maxn<<1];
int ulen;
struct Line
{
double x;
double y_up,y_down;
int dir; //1为入边,-1为出边
}L[maxn<<1]; //2*n的边数
struct Tree
{
int l,r;
int cover;
double len;
}tree[maxN<<2];
bool cmp(Line a,Line b)
{
return a.x<b.x;
}
void build(int i,int l,int r)
{
tree[i].l=l;
tree[i].r=r;
tree[i].cover=0;
tree[i].len=0.0;
if(l+1==r)
return;
int m=(l+r)>>1;
build(i<<1,l,m);
build(i<<1|1,m,r);
}
int find(double x)
{
int l=1,r=ulen;
while(l<=r)
{
int mid=(l+r)/2;
if(fabs(y[mid]-x)<1e-6) // if(y[mid]==x)
return mid;
if(y[mid]>x)
r=mid-1;
else
l=mid+1;
}
return l;
}
void fun(int i) //包含pushup和完全覆盖的情况下更新
{
if(tree[i].cover>0) //有覆盖则整一段可以做为下一条线段计算用
tree[i].len=(y[tree[i].r]-y[tree[i].l]);
else if(tree[i].l+1==tree[i].r) //叶子
tree[i].len=0;
else tree[i].len=tree[i<<1].len+tree[i<<1|1].len;
}
void update(int i,int l,int r,int dir)
{
if(tree[i].l>r||tree[i].r<l) //完全不搭
return;
if(l<=tree[i].l&&tree[i].r<=r) //完全包含
{
tree[i].cover+=dir;
fun(i);
return;
}
update(i<<1,l,r,dir);
update(i<<1|1,l,r,dir);
fun(i);
}
int main()
{
int n;
double x1,x2,y1,y2;
int te=1;
RE
while(cin>>n,n)
{
int cnt=1;
FOR(i,1,n)
{
cin>>x1>>y1>>x2>>y2;
L[cnt].x=x1;L[cnt].y_down=y1;L[cnt].y_up=y2;L[cnt].dir=1;y[cnt]=y1;cnt++;
L[cnt].x=x2;L[cnt].y_down=y1;L[cnt].y_up=y2;L[cnt].dir=-1;y[cnt]=y2;cnt++;
} //cnt==2*n+1
sort(L+1,L+cnt,cmp); //线段按x排序
sort(y+1,y+cnt); //y数组保存y值,升序,才与线段树区间对应
ulen=1;
y[0]=-100005.0;
for(int i=1;i<cnt;i++) //y数组去重,默认y[0]与y[1]不同
{
if(y[i]!=y[i-1])
{
y[ulen++]=y[i];
}
}
ulen--; // !!!!!
build(1,1,ulen);
double ans=0.0;
FOR(i,1,cnt-1)
{ //在新线的y区间插入新cover(dir)
int a=find(L[i].y_down); //在y数组里找y_down的下标
int b=find(L[i].y_up);
update(1,a,b,L[i].dir); //每插入一条就把这条与下一条能算的算起来
ans+=tree[1].len*(L[i+1].x-L[i].x); //tree[1].len为整个树能与i+1条线计算的,因为有pushup操作
}
cout<<"Test case #"<<te++<<endl;
cout<<setiosflags(ios::fixed);
cout<<"Total explored area: "<<setprecision(2)<<ans<<endl<<endl;
}
return 0;
}
既然是整型的l,r,那就按常规的线段树飘逸去吧,把tree结构体省了,添加参数代替。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <set>
#include <map>
#include <stack>
#include <queue>
#include <vector>
#include <iostream>
#include <iomanip>
#include <algorithm>
using namespace std;
#define ll __int64
#define INF 0x7FFFFFFF
#define INT_MIN -(1<<31)
#define eps 10^(-6)
#define Q_CIN ios::sync_with_stdio(false);
#define REP( i , n ) for ( int i = 0 ; i < n ; ++ i )
#define FOR( i , a , b ) for ( int i = a ; i <= b ; ++ i )
#define CLR( a , x ) memset ( a , x , sizeof (a) );
#define RE freopen("1.in","r",stdin);
#define WE freopen("1.out","w",stdout);
#define MOD 10009
#define debug(x) cout<<#x<<":"<<(x)<<endl;
#define lson i<<1,l,m
#define rson i<<1|1,m,r
const int maxn=105;
const int maxN=100010;
double y[maxn<<1];
int ulen;
struct Line
{
double x;
double y_up,y_down;
int dir; //1为入边,-1为出边
bool operator <(const struct Line &another)const{
return x<another.x;
}
}L[maxn<<1];
int cover[maxN<<2];
double len[maxN<<2];
void build(int i,int l,int r)
{
cover[i]=0;len[i]=0.0;
if(l+1==r)
return;
int m=(l+r)>>1;
build(lson);
build(rson);
}
void fun(int i,int l,int r) //包含pushup和完全覆盖的情况下更新
{
if(cover[i]>0) len[i]=(y[r]-y[l]);
else if(l+1==r) len[i]=0;
else len[i]=len[i<<1]+len[i<<1|1];
}
void update(int i,int l,int r,int L,int R,int dir)
{
if(L<=l&&r<=R)
{
cover[i]+=dir;
fun(i,l,r);
return;
}
int m=(l+r)>>1;
if(L<m) //注意这里没有等号,如[1,4]里插入[2,4],不能去左子区间[1,2]找
update(lson,L,R,dir);
if(R>m)
update(rson,L,R,dir);
fun(i,l,r);
}
int main()
{
int n;
double x1,x2,y1,y2;
int te=1;
// RE
while(cin>>n,n)
{
int cnt=1;
FOR(i,1,n)
{
cin>>x1>>y1>>x2>>y2;
L[cnt].x=x1;L[cnt].y_down=y1;L[cnt].y_up=y2;L[cnt].dir=1;y[cnt]=y1;cnt++;
L[cnt].x=x2;L[cnt].y_down=y1;L[cnt].y_up=y2;L[cnt].dir=-1;y[cnt]=y2;cnt++;
} //cnt==2*n+1
sort(L+1,L+cnt);
sort(y+1,y+cnt);
ulen=1;
y[0]=-100005.0;
for(int i=1;i<cnt;i++)
if(y[i]!=y[i-1])
y[ulen++]=y[i];
ulen--;
build(1,1,ulen);
double ans=0.0;
FOR(i,1,cnt-1)
{
int a=lower_bound(y+1,y+ulen,L[i].y_down)-y;
int b=lower_bound(y+1,y+ulen,L[i].y_up)-y;//因为知道元素一定存在
update(1,1,ulen,a,b,L[i].dir);
ans+=len[1]*(L[i+1].x-L[i].x);
}
printf("Test case #%d\nTotal explored area: %.2lf\n\n",te++,ans);
}
return 0;
}