扫描线--线段树

今天和大家结束一下扫描线算法。我尽量详细的解释。
扫描线?是干什么的? 有一道很简单的例题 二维平面有n个平行于坐标轴的矩形,现在要求出这些矩形的总面积. 重叠部分只能算一次。

input :
3 (n个矩形)

1 1 4 4 (左下角与右上角)

3 2 6 3

5 1 8 4

在这里插入图片描述这是正常人的求法, 要是换成计算机 该怎么求面积呢?

计算机 先把矩形数据存在一个结构体里

struct line{
    /* l r是指这个矩形的平行与x轴的线 左边的x坐标 与右边 x的坐标*/
    /* h值的不是矩形的高度  而是矩形平行与x轴 那条线的高度
        因此一个矩形有两个 平行与x轴的线 所有数组要开2*N*/
    int l,r,h,
    int c;//c是指这条线属于矩形的底边还是上边 底边为1,上边为1
}lin[2*N];

然后把 这些数据按 h的大小从小到大排序

按h 从小到大排序后 得到
1 l=1 , r=4 ,h =1 ,c=1;
2 l=5 , r =8, h=1 ,c=1;
3 l=3 , r=6, h=2 ,c=1;
4 l=3 , r=6, h=3, c= -1;
5 l=1, r=4 , h=4 ,c= -1;
6 l=5, r=8, h=4 ,c=-1;

首先 我加h最小的 线 插入线段树里 也就是把 l=1 ,r=4,c=1; (l 到 r 的区间加c);
然后 在查询 线段是有哪些线段被覆盖 了 (也就是 tree[node]>0),设得到的值 为w
再将 w与 它的上条线段的高度差值 相乘 也就是 ans+=w*(lines[i+1].h-lines[i].h);
然后慢慢的就能求出面积 。自己动手写下就知道了。
代码:

#include<iostream>
#include<algorithm>
#include<vector>
#include<stdio.h>
using namespace std;
const int N=1e5+7;
int maxn=1e9+7;

int x1,x2,y1,y2;

struct line{
    int l,r,h;
    int c;
}lin[2*N];

struct node{
    int cover,l,r;
}tree[16*N];


bool cmp(line x,line y){
    return x.h<y.h;
}
vector<int>x;
#define m (l+r)/2
#define lson 2*node
#define rson 2*node+1

void build(int l,int r,int node){
    tree[node].l=x[l-1];
    tree[node].r=x[r-1];
    tree[node].cover=0;
    if(r-l==1)return;
    build(l,m,lson);
    build(m,r,rson);
}

void update(int v,int ql,int qr,int node){
    if(ql>=tree[node].r||tree[node].l>=qr)return;
    if(ql<=tree[node].l&&qr>=tree[node].r){
        tree[node].cover+=v;
        return;
    }
    if(tree[node].r-tree[node].l==1)return;
    
    update(v,ql,qr,lson);
    update(v,ql,qr,rson);
}

int query(int node){
    if(tree[node].l==0&&tree[node].r==0)return 0;
    if(tree[node].cover>0){
        return tree[node].r-tree[node].l;
    }
    int ans=0;
    ans+=query(lson);
    ans+=query(rson);
    return ans;
}


int main(){
    int n,cnt=1;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        lin[cnt].l=x1,lin[cnt].r=x2,lin[cnt].h=y1,lin[cnt++].c=1;
        lin[cnt].l=x1,lin[cnt].r=x2,lin[cnt].h=y2,lin[cnt++].c=-1;
        x.push_back(x1);
        x.push_back(x2);
    }
    sort(lin+1,lin+cnt,cmp);
    sort(x.begin(),x.end());
    x.erase(unique(x.begin(),x.end()),x.end());
    build(1,x.size(),1);
    long long area=0;
    for(int i=1;i<cnt-1;i++){
        update(lin[i].c,lin[i].l,lin[i].r,1);
        int width=query(1); 
        area+=1ll*width*(lin[i+1].h-lin[i].h);
    }
    printf("%lld\n",area);
}

难点: 这里线段树建树与以前的线段树有所不同 ,是m到r建树不是 m+ 1到 r 建树,原因其实很简单 这里是不区间查询 l到r是tree【node】>0,而是查询 整棵树上tree[node]>0的区间和 ,如果还是像以前一样建树会导致两个叶子节点的值为0 ;
自己动手画下就知道了。

在来看到扫描求周长的题

Picture

其实和球面积差不多 横着扫一遍边长 竖着在扫一遍边长。就可以了 注意 这里的线不仅要按h优先还要按 c大优先 自己想一下就知道了 。
代码

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cstdio>
const int N=5e4+7;
using namespace std;
struct line{
    int l,r,h,c;
}lines[2*N];

vector<int>x;

struct node{
    int c,l,r;
}tree[4*N];

struct edge{
    int x1,y1,x2,y2;
}p[N];

bool cmp(line a,line b){
    if(a.h==b.h)return a.c>b.c;
    return a.h<b.h;
}

#define m (l+r)/2
#define lson 2*node
#define rson 2*node+1

void build(int l,int r,int node){
    tree[node].l=x[l-1];
    tree[node].r=x[r-1];
    if(r-l==1)return;
    build(l,m,lson);
    build(m,r,rson);
}

void update(int v,int ql,int qr,int node){
    if(tree[node].l>=qr||tree[node].r<=ql)return;
    if(ql<=tree[node].l&&qr>=tree[node].r){
        tree[node].c+=v;
        return;
    }
    update(v,ql,qr,lson);
    update(v,ql,qr,rson);
}

int query(int node){
    if(tree[node].c)return abs(tree[node].r-tree[node].l);
    if(tree[node].l==0&&tree[node].r==0)return 0;
    int ans=0;
    ans+=query(lson);
    ans+=query(rson);
    return ans;
}


int main(){
    int n,cnt=1;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d%d%d%d",&p[i].x1,&p[i].y1,&p[i].x2,&p[i].y2);
        lines[cnt].l=p[i].x1,lines[cnt].r=p[i].x2,lines[cnt].h=p[i].y1,lines[cnt++].c=1;
        lines[cnt].l=p[i].x1,lines[cnt].r=p[i].x2,lines[cnt].h=p[i].y2,lines[cnt++].c=-1;
        x.push_back(p[i].x1);x.push_back(p[i].x2);
    }
    sort(x.begin(),x.end());
    x.erase(unique(x.begin(),x.end()),x.end());
    sort(lines+1,lines+cnt,cmp);
    build(1,x.size(),1);
    int last=0,ans=0,now=0;
    for(int i=1;i<cnt;i++){
        update(lines[i].c,lines[i].l,lines[i].r,1);
        now=query(1);
        ans=ans+abs(now-last);
        last=now;
    }
    cnt=1;x.clear();
    for(int i=1;i<=n;i++){
        lines[cnt].l=p[i].y1,lines[cnt].r=p[i].y2,lines[cnt].h=p[i].x1,lines[cnt++].c=1;
        lines[cnt].l=p[i].y1,lines[cnt].r=p[i].y2,lines[cnt].h=p[i].x2,lines[cnt++].c=-1;
        x.push_back(p[i].y1),x.push_back(p[i].y2);
    }
    memset(tree,0,sizeof(tree));
    sort(x.begin(),x.end());
    x.erase(unique(x.begin(),x.end()),x.end());
    sort(lines+1,lines+cnt,cmp);
    build(1,x.size(),1);
    last=0,now=0;
     for(int i=1;i<cnt;i++){
        update(lines[i].c,lines[i].l,lines[i].r,1);
        now=query(1);
        ans=ans+abs(now-last);
        last=now;
    }
    printf("%d\n",ans);

}
发布了10 篇原创文章 · 获赞 1 · 访问量 184

猜你喜欢

转载自blog.csdn.net/weixin_45676038/article/details/102898709