AcWing 算法提高课 线段树+扫描线法 求矩形之并的面积

例题:求解多个长方形之并的面积

https://www.acwing.com/problem/content/249/

蓝色表示长方形,红色表示扫描线

如下图所示,对于每一个横向的区间,在纵向维护线段树

根据纵向的累计长度,即可对每个横向区间求出面积

求面积的过程中,可以从左到右遍历区间(遍历除第一个点以外的分界点),遇到左边界,则将线段对应全部位置+1,右边界则-1。此时面积就是区间长度*全部大于0的位置的数量。

 

 

由于扫描线的性质(只查询节点1、加和减成对出现、cnt大于0则不需要向下递归等性质),不需要使用懒标记。只需维护当前节点的cnt和len,cnt表示当前区间整个被覆盖的次数,len表示cnt>0的区间总长度。

代码:(注意,例题中由于y存在小数,故需要离散化,并且线段树中的"点"代表一个区间)

 

#include<bits/stdc++.h>

#define fore(x,y,z) for(LL x=(y);x<=(z);x++)
#define forn(x,y,z) for(LL x=(y);x<(z);x++)
#define rofe(x,y,z) for(LL x=(y);x>=(z);x--)
#define rofn(x,y,z) for(LL x=(y);x>(z);x--)
#define pub push_back
#define all(x) (x).begin(),(x).end()
#define fi first
#define se second

using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
typedef pair<LL,LL> PLL;
const int N=100010;
int T=1;
struct Segment
{
    double x,y1,y2;
    int k;
    bool operator<(const Segment &t) const
    {
        return x<t.x;
    }
}seg[N*2];//长方形的左右边,即需要用于添加和删除的线段
struct Node
{
    int l,r;
    int cnt;
    double len;
}tr[N*8];//最多2N个点,2*N-1个区间
int n;
vector<double> ys;//y离散化
int find(double y)//找到离散化后对应的下标
{
    return lower_bound(ys.begin(),ys.end(),y)-ys.begin();
}

void pushup(int u)
{
    if(tr[u].cnt) tr[u].len=ys[tr[u].r+1]-ys[tr[u].l];//区间右端点是y[r+1]
    else if(tr[u].l!=tr[u].r)
    {
        //由于modify中使用了pushup进行计算,
        //而没有对叶节点进行特殊,所以需要在这里判断
        tr[u].len=tr[u*2].len+tr[u*2+1].len;
    }
    else
        tr[u].len=0;
}
void build(int u,int l,int r)
{
    tr[u]={l,r,0,0};
    if(l!=r)
    {
        int mid=(l+r)/2;
        build(u*2,l,mid);
        build(u*2+1,mid+1,r);
    }
}

void modify(int u,int l,int r,int k)
{
    if(tr[u].l>=l&&tr[u].r<=r)
    {
        tr[u].cnt+=k;
        pushup(u);
    }
    else
    {
        int mid=(tr[u].l+tr[u].r)/2;
        if(l<=mid) modify(u*2,l,r,k);
        if(r>=mid+1) modify(u*2+1,l,r,k);
        pushup(u);
    }
}
void YD()
{
    ys.clear();
    int cnt=0;
    fore(i,1,n)
    {
        double x1,x2,y1,y2;
        cin>>x1>>y1>>x2>>y2;
        seg[cnt++]={x1,y1,y2,1};
        seg[cnt++]={x2,y1,y2,-1};
        ys.pub(y1);ys.pub(y2);
    }
    sort(ys.begin(),ys.end());
    ys.erase(unique(ys.begin(),ys.end()),ys.end());
    sort(seg,seg+2*n);
    build(1,0,ys.size()-2);//每个y代表其后面的区间,故不需要建立最后一个y
    double res=0;
    fore(i,0,2*n-1)
    {
        if(i>0) res+=tr[1].len*(seg[i].x-seg[i-1].x);
        modify(1,find(seg[i].y1),find(seg[i].y2)-1,seg[i].k);
        //对于n个点共有n-1个区间,可以用每一个点代表以其为左端点的区间,
        //故find(seg[i].y2-1),表示不修改y2后面的区间。
    }
    cout<<"Test case #"<<T<<endl;
    cout<<"Total explored area: "<<fixed<<setprecision(2)<<res<<endl; 
    cout<<endl;
}
 
int main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    
    while (cin>>n,n)
    {
        YD();
        T++;
    }
    return 0;
}
View Code

 

posted @ 2022-09-27 19:34  80k  阅读(28)  评论(0编辑  收藏  举报