线段树应用:扫描线

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sdz20172133/article/details/84193793

扫描线暴力解决的话时间和空间复杂度往往是不够的。

所以,扫描线也就成了线段树很大的应用。

具体原理解释(写得很好):

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(线段树:扫描线)

猜你喜欢

转载自blog.csdn.net/sdz20172133/article/details/84193793