1. 程式人生 > >線段樹應用:掃描線

線段樹應用:掃描線

掃描線暴力解決的話時間和空間複雜度往往是不夠的。

所以,掃描線也就成了線段樹很大的應用。

具體原理解釋(寫得很好):

https://blog.csdn.net/u013480600/article/details/22548393

https://blog.csdn.net/zearot/article/details/48299459#t19

https://www.cnblogs.com/scau20110726/archive/2013/04/12/3016765.html(圖片很好)

給出模板:第一篇部落格(饒齊大佬)的修正與改進,還有一些地方的解釋,錯誤原因

第一個:

我們線上段樹更新的時候,是這樣往下走的:    
sumv[O]=sumv[O*2]+sumv[O*2+1].
如果左子樹維護的區間[l,mid],
右子樹維護的區間[mid+1,r]
那麼還有一個區間[mid,mid+1]沒有統計到,怎麼辦?
下面程式碼解釋是左閉右開,請讀者自行思考。

/* 我們線上段樹更新的時候,是這樣往下走的:    
sumv[O]=sumv[O*2]+sumv[O*2+1].
如果左子樹維護的區間[l,mid],
右子樹維護的區間[mid+1,r]
那麼還有一個區間[mid,mid+1]沒有統計到,怎麼辦?
請讀者自行思考。*/
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2222;
#define lson i<<1,l,m
#define rson i<<1|1,m+1,r
#define root 1,1,k-1
double X[MAXN];
struct node
{
    double l,r,h;
    int d;  ///d為1或-1,標記掃描線是矩形的上位還是下位邊.
    node(){}
    node(double a,double b,double c,int d): l(a),r(b),h(c),d(d){}
    bool operator <(const node &b)const
    {
        return h<b.h;
    }
}nodes[MAXN];
///cnt: >=0時表示本節點控制的區域內下位邊個數-上位邊個數的結果.
       ///如果==-1時,表示本節點左右子樹的上下位邊數不一致.
///sum: 本節點控制的區域內cnt值不為0的區域總長度.
int cnt[MAXN*4];
double sum[MAXN*4];
///如果cnt!=-1,那麼下放cnt資訊,並更新子節點的sum資訊.
void PushDown(int i,int l,int r)
{
    int m=(l+r)>>1;
    if(cnt[i]!=-1)
    {
        cnt[i<<1]=cnt[i<<1|1]=cnt[i];
        sum[i<<1]= (cnt[i]?(X[m+1]-X[l]):0) ;
        sum[i<<1|1]= (cnt[i]?(X[r+1]-X[m+1]):0) ;
    }
}
///根據子節點的cnt值和sum值更新父節點的cnt和sum值
void PushUp(int i,int l,int r)
{
    if(cnt[i<<1]==-1 || cnt[i<<1|1]==-1)
        cnt[i]=-1;
    else if(cnt[i<<1] != cnt[i<<1|1])
        cnt[i]=-1;
    else
        cnt[i]=cnt[i<<1];
    sum[i]=sum[i<<1]+sum[i<<1|1];
}

void update(int ql,int qr,int v,int i,int l,int r)
{
    if(ql<=l && r<=qr)
    {
        if(cnt[i]!=-1)
        {
            cnt[i]+=v;
            sum[i] = (cnt[i]? (X[r+1]-X[l]):0);
            return ;
        }
    }
    PushDown(i,l,r);
    int m=(l+r)>>1;
    if(ql<=m) update(ql,qr,v,lson);
    if(m<qr)  update(ql,qr,v,rson);
    PushUp(i,l,r);
}
///二分尋找區間 ,每一個掃描線
int bin(double key,int n,double d[])
{
    int l=1,r=n;
    while(r>=l)
    {
        int m=(r+l)>>1;
        if(d[m]==key)
            return m;
        else if(d[m]>key)
            r=m-1;
        else
            l=m+1;
    }
    return -1;
}
int main()
{
    int q;
    int kase=0;
    while(scanf("%d",&q)==1&&q)
    {
        int n=0,m=0;
        for(int i=1;i<=q;i++)
        {
            double x1,y1,x2,y2;
            scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
            X[++n]=x1;
            nodes[++m]=node(x1,x2,y1,1);
            X[++n]=x2;
            nodes[++m]=node(x1,x2,y2,-1);
        }
        sort(X+1,X+n+1);
        sort(nodes+1,nodes+m+1);
        int k=unique(X+1,X+n+1)-(X+1);//去掉相鄰的重複元素
        //build(1,1,k-1);
        memset(sum,0,sizeof(sum));
        memset(cnt,0,sizeof(cnt));
        double ret=0.0;//最終面積
        for(int i=1;i<m;i++)
        {
            int l=bin(nodes[i].l,k,X);  
            int r=bin(nodes[i].r,k,X)-1;///左開右閉的性質
            //cout<<l<<" "<<r<<endl;
            if(l<=r) update(l,r,nodes[i].d,root);
            ret += sum[1]*(nodes[i+1].h-nodes[i].h);
        }
        printf("Test case #%d\nTotal explored area: %.2lf\n\n",++kase,ret );
    }
}

經典例題:

HDU 1542 Atlantis(線段樹:掃描線)