hdu 1542 矩形面积并(扫描线+线段树)

【题目链接】
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<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
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 h<a.h;
    }
};
Type seg[maxn];

double 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;i<k;i++ )
    {
        if( 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( L<R )
    {
        sumv[O]=sumv[O*2]+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; i<k; i++ )
        {
            int 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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章