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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章