【题目链接】
http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=14795
【解题报告】
七月的时候做过扫描线线段树,那个时候觉得好难TAT….
当然现在看是很基础的题目了。
这道题根本上还是一个扫描线,不过由于是二维状态下的扫描,所以裸的扫描线是不可取的。因为那意味着O(n^2)的操作。(想想为什么不像一维的扫描线一样只取一段区间的端点值?)
那我们能不能在某一维实现降低复杂度的操作呢?
容易想到,用线段树来维护。这样一维O(N),一维O(logN),可以愉快的扫描线了!
线段树对我们来说是一个高效维护、查询、修改线性数据的工具,所以凡是涉及到线性数据的题目,我们大可以都想想能不能使用线段树来维护一下。
对矩形面积并来说就是如此,与其说是“线段树扫描线”的这样一个算法,倒不如说为了维护扫描线算法,用到了线段树操作。
线段树有什么用呢?
我们可以很快的来维护:扫描到当前线段时,多条线段的作用域叠加到一起的线段总长度。
那么每次我们扫描到一条线段,直接拿线段总长,乘上当前线段和下条线段之间的高度。
对一个矩形来说,它的对边,可以认为是一条边扫进,一条边扫出。扫进的线段就线段树update加上,扫出的线段就update减去。
然后我们对线段树的每个节点维护的区间值表示:当前区间的线段总长度(注意,线段可不一定是连续线段)。
那么每次维护之后我们只取线段树的根节点的区间值即可。
另外,还有一个需要注意的点是,
我们在线段树更新的时候,是这样往下走的:
sumv[O]=sumv[O*2]+sumv[O*2+1].
如果左子树维护的区间[l,mid],
右子树维护的区间[mid+1,r]
那么还有一个区间[mid,mid+1]没有统计到,怎么办?
请读者自行思考。
至于矩形面积并的求解方法,和扫描线的具体细节,以及如何把线段树和扫描线结合,网上大量blog都有详细清晰的说明。这里不再赘述。如果读者觉得理解起来吃力,代码更是两眼一抹黑,说明你像七月份的我一样基础非常不扎实,这时候强行看扫描线线段树的效果并不好。所以请先去分别学习扫描线和线段树的知识。
【参考代码】
#include
#include
#include
#include
#include
using namespace std;
const int maxn=1e5+50;
struct Type
{
double l,r,h; //以横坐标建立线段树,纵坐标为高度
int cur; //cur=-1说明是出边,cur=1说明是入边
void sets( double x1, double x2, double h, int cur )
{
this->l=x1; this->r=x2; this->h=h; this->cur=cur;
}
bool operator < ( const Type& a )const
{
return hdouble sumv[maxn*4]; //用来维护线段并
int cntv[maxn*4]; //用来维护当前节点代表的区间被覆盖了几次
double point[maxn*2];//用来维护离散化后的端点
int n;
int uniq(int k)
{
int m=1;
sort( point+1,point+1+k );
for( int i=1;iif( point[i]!=point[i+1] )point[++m]=point[i+1];
}
return m;
}
void build( int O,int L,int R )
{
if(L==R){ sumv[O]=0; cntv[O]=0; }
else
{
int mid=(L+R)/2;
build( O*2,L,mid );
build( O*2+1,mid+1,R );
sumv[O]=0; cntv[O]=0;
}
}
void maintain( int O, int L, int R )
{
if( cntv[O] )
{
sumv[O]=point[R+1]-point[L];
}
else if( L2]+sumv[O*2+1];
// cntv[O]=min( cntv[O*2],cntv[O*2+1] );
}
else { sumv[O]=0; cntv[O]=0; }
}
void pushdown( int O )
{
if( cntv[O] )
{
cntv[O*2]=cntv[O*2+1]=cntv[O];
cntv[O]=0;
sumv[O]=0;
}
}
void update( int O, int L, int R, int qL, int qR,int op )
{
if( qL<=L && R<=qR )
{
cntv[O]+=op;
}
else
{
// pushdown(O); //pushdown其实是不需要的,因为我们遇到一个cnt就可以返回整段信息,也就是说,更深的cnt信息不需要考虑,所以cnt没必要下传
int mid=(L+R)/2;
if( qL<=mid )update( O*2,L,mid,qL,qR,op );
if( qR>mid )update( O*2+1,mid+1,R,qL,qR,op );
}
maintain( O,L,R );//重新计算sumv[O];
}
int main()
{
int kase=0;
while( ~scanf("%d",&n) &&n )
{
int k=0;
for( int i=1; i<=n; i++ )
{
double x1,x2,y1,y2;
scanf( "%lf%lf%lf%lf",&x1,&y1,&x2,&y2 );
seg[++k].sets( x1,x2,y1,-1 );
point[k]=x1;
seg[++k].sets( x1,x2,y2,1 );
point[k]=x2;
}
int m=uniq(k); //对端点进行了离散化
sort( seg+1,seg+1+k ); //所有k条线段从低到高排序
build( 1,1,m );
double ans=0;
for( int i=1; iint L=lower_bound( point+1,point+1+m,seg[i].l )-point;
int R=lower_bound( point+1,point+1+m,seg[i].r )-point-1;
update( 1,1,m,L,R,seg[i].cur );
ans+=sumv[1]*( seg[i+1].h-seg[i].h );
}
printf("Test case #%d\nTotal explored area: %.2lf\n\n",++kase,ans);
}
return 0;
}