扫描线——一种巧妙的技巧求面积

请在学习之前有一定的线段树基础

在一些题中,它总会给你一些矩形,之后让你求总覆盖面积。

它的难点在于,有重叠面积,如果只是罗列情况,那么只会一事无成。

所以说,这里就引进了扫描线做法;

其实它的原理很简单,只是底*高而已,只是分段求解;

而问题大概的图就是这样

根据我刚刚说的分段求解和底*高,那么我们就可以推测出扫描线是什么了

它是由矩形的上边和下边构成,并记录其左右端点和其所在的纵坐标;

图中标红的即为扫描线,那么我们用它做什么?

根据 S=a*h,那么我们可以将扫描线按纵坐标排序,这样分步求解。

这是扫描线的储存

struct node{
    int x,y;//左右端点坐标 
    int h;//还是按 扫描线写,这个是y轴坐标 
    int d;//标记这个线是不是上界或下界 
}s[maxn<<3];//扫描线 

那么便可以得到高,即

s[i+1].h-s[i].h

之后考虑存底,只需要用线段树维护即可;

当扫描线为下界时,应当将扫描线所在区域加入线段树,而当为上界时再减去即可;

由于底边过大,不可能全部建树,这里给出了离散化做法,还有动态开点做法之后将会提到

由于找不到最合适的模板题,只能拿这个来充数P2061 [USACO07OPEN]城市的地平线City Horizon

   scanf("%d",&n);
    for(int i=1,a,b,h;i<=n;i++){
        scanf("%d%d%d",&a,&b,&h);
        ls[++cent]=a;//其实是用来离散化的 
        s[cent]=(node){a,b,0,1};
        ls[++cent]=b;
        s[cent]=(node){a,b,h,-1};
    }
    sort(ls+1,ls+1+cent);//离散化初始 
    sort(s+1,s+1+cent);ls[++m]=ls[1];
    for(int i=2;i<=cent;i++){
        if(ls[i]!=ls[i-1])
            ls[++m]=ls[i];//去重 
    }

这便是简单的离散化,当然,你也可以排序后用unique函数,得到m和ls数组,这个可以网上查询,这里便不再赘述

之后是线段树

void push_up(int l,int r,int p){
    if(mark[p]) tree[p].sum=ls[r]-ls[l];//如何避免少减? 
    else if(l==r) tree[p].sum=0;
    else tree[p].sum=tree[le(p)].sum+tree[re(p)].sum;
}

void up_date(int p,int d,int L,int R){
    int l=tree[p].l,r=tree[p].r;
    if(l>=L&&r<=R){
        mark[p]+=d;
        push_up(l,r,p);
        return ;
    }
    if(r-1==l) return ;
    int mid=l+r>>1;
    if(mid>=L) up_date(le(p),d,L,R);
    if(mid<R) up_date(re(p),d,L,R);
    push_up(l,r,p);
}

这里的代码是我根据多种方面得出,但是仍由问题,即代码所说的,因为在线段数中 l 是可以等于 r 的,但是线段的长度必须由两个不同的数得出,这是不行的

所以,我们可以先建出一颗空树

void build(int l,int r,int p){
    tree[p].l=l;
    tree[p].r=r;
    tree[p].sum=0;
    if(l==r-1) return ;
    int mid=l+r>>1;
    build(l,mid,le(p)),build(mid,r,re(p));/*注意,mid在左子树和右子树中都有出现,所以在 
    叶子节点,r=l+1,这个也是对return 的解释*/ 
}

所以说,这样便避免了这个问题,不过请读者注意这些点,这些便是易错的小细节

    build(1,m,1);
    for(int i=1;i<=cent;i++){
        int l=search(s[i].x,ls);//二分寻找离散化位置
        int r=search(s[i].y,ls);
        up_date(1,s[i].d,l,r);// 用线段树更新sum,即矩形底边 
        rt+=(ll)tree[1].sum*1ll*(s[i+1].h-s[i].h);
    }

这里是主函数的计算,而search有解释,也可以用lower_bound,推荐提前处理出来,否则可能会提高时间复杂度,这不是我们所期望的

这样的做法不易错是真的,这里给出二分search做法

int search(int pur,int* x){
    int l=1,r=m;
    while(l<r){
        int mid=l+r>>1;
        if(x[mid]<pur) l=mid+1;
        else r=mid;
    }
    return l;
}

这便是整个过程,这里给出code

Code

#include<bits/stdc++.h>
#define ll long long
#define maxn 40007
#define le(x) x<<1
#define re(x) x<<1|1
using namespace std;
int n,cent,m,mark[maxn<<4];
int ls[maxn<<3];
ll rt;
struct tr{
    ll sum;
    int l,r;
}tree[maxn<<4];
struct node{
    int x,y;//左右端点坐标 
    int h;//还是按 扫描线写,这个是y轴坐标 
    int d;//标记这个线是不是上界或下界 
}s[maxn<<3];//扫描线 

bool operator <(node a,node b){
    return a.h<b.h;
}

void push_up(int l,int r,int p){
    if(mark[p]) tree[p].sum=ls[r]-ls[l];//如何避免少减? 
    else if(l==r) tree[p].sum=0;
    else tree[p].sum=tree[le(p)].sum+tree[re(p)].sum;
}

void up_date(int p,int d,int L,int R){
    int l=tree[p].l,r=tree[p].r;
    if(l>=L&&r<=R){
        mark[p]+=d;
        push_up(l,r,p);
        return ;
    }
    if(r-1==l) return ;
    int mid=l+r>>1;
    if(mid>=L) up_date(le(p),d,L,R);
    if(mid<R) up_date(re(p),d,L,R);
    push_up(l,r,p);
}

void build(int l,int r,int p){
    tree[p].l=l;
    tree[p].r=r;
    tree[p].sum=0;
    if(l==r-1) return ;
    int mid=l+r>>1;
    build(l,mid,le(p)),build(mid,r,re(p));/*注意,mid在左子树和右子树中都有出现,所以在 
    叶子节点,r=l+1,这个也是对return 的解释*/ 
}

int search(int pur,int* x){
    int l=1,r=m;
    while(l<r){
        int mid=l+r>>1;
        if(x[mid]<pur) l=mid+1;
        else r=mid;
    }
    return l;
}

int main(){
//    freopen("cin.in","r",stdin);
    scanf("%d",&n);
    for(int i=1,a,b,h;i<=n;i++){
        scanf("%d%d%d",&a,&b,&h);
        ls[++cent]=a;//其实是用来离散化的 
        s[cent]=(node){a,b,0,1};
        ls[++cent]=b;
        s[cent]=(node){a,b,h,-1};
    }
    sort(ls+1,ls+1+cent);//离散化初始 
    sort(s+1,s+1+cent);ls[++m]=ls[1];
    for(int i=2;i<=cent;i++){
        if(ls[i]!=ls[i-1])
            ls[++m]=ls[i];//去重 
    }
    build(1,m,1);
    for(int i=1;i<=cent;i++){
        int l=search(s[i].x,ls);//二分寻找离散化位置
        int r=search(s[i].y,ls);
        up_date(1,s[i].d,l,r);// 用线段树更新sum,即矩形底边 
        rt+=(ll)tree[1].sum*1ll*(s[i+1].h-s[i].h);
    }
    cout<<rt<<endl;
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/waterflower/p/11100970.html