Atlantis POJ1151(线段树扫描线)

题目大意
给你n个矩形,让你求所有矩形覆盖的总面积,重叠的地方只算一次
输入格式
输入的第一行包含一个整数n,表示可得到的地图数目。
以下n行,每行描述一张地图。每行包含4个整数x1,y1,x2和y2(0≤x1<x2≤30000,0≤y1<y2≤30000)。数值(x1,y1)和(x2,y2)是坐标,分别表示绘制区域的左下角和右上角坐标。每张地图是矩形的,并且它的边是平行于x坐标轴或y坐标轴的。
数据可能有多组,当n为零时停止输入
输出格式
对于每个测试数据,你的程序应该输出一个答案。每个答案的第一行必须是“Test case #k”,其中k是测试数据的编号(从1开始)。第二个必须是“Total explored area: a”,其中a是总探索面积(即在本测试案例中所有矩形的并集面积),精确到小数点右边两位。
在每个测试用例后输出一个空行。
样例输入
2
10 10 20 20
15 15 25 25.5
0

样例输出
Test case #1
Total explored area:180.00
算法思路:
这题是我线段树扫描线,题目给了n个矩形,每个矩形给了左下角和右上角的坐标,矩形可能会重叠,求的是矩形最后的面积。因为变化范围比较大,我们要用到离散化,离散化就不说了,重点说一说扫描线的过程:
看下图:
在这里插入图片描述
现在假设我们有一根线,从下往上开始扫描
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如图所示,我们可以把整个矩形分成如图各个颜色不同的小矩形,那么这个小矩形的高就是我们扫过的距离,那么剩下了一个变量,那就是矩形的长一直在变化。

我们的线段树就是为了维护矩形的长,我们给每一个矩形的上下边进行标记,下面的边标记为1,上面的边标记为-1,每遇到一个矩形时,我们知道了标记为1的边,我们就加进来这一条矩形的长,等到扫描到-1时,证明这一条边需要删除,就删去,利用1和-1可以轻松的到这种状态。

也可以灵活应用,上图是水平扫描线,也可以垂直扫描线,这样,扫过的矩形的长是固定的,矩形的高度一直在变化,这样我们就给每一个矩形的左右边进行标记,左边的边标记为1,右面的边标记为-1,每遇到一个矩形时,我们知道了标记为1的边,我们就加进来这个矩形的高,扫到-1时,就减去这个矩形的高。如下图:

在这里插入图片描述
再提一下离散化,离散化就是把一段很大的区间映射到一个小区间内,这样会节省大量空间,要进行离散化,我们先对端点进行排序,然后去重,然后二分找值就可以了。

代码:

#include<cstdio>
#include<cstring>
#include<cctype>
#include<string>
#include<set>
#include<iostream>
#include<stack>
#include<cmath>
#include<queue>
#include<vector>
#include<algorithm>
#define mem(a,b) memset(a,b,sizeof(a))
#define inf 0x3f3f3f3f
#define N 220
#define ll long long
using namespace std;
#define lson l,m,rt<<1//左子树区间
#define rson m+1,r,rt<<1|1//右子树区间
struct Seg
{
    double l,r,h;
    int f;
    Seg() {}
    Seg(double a,double b,double c,int d):l(a),r(b),h(c),f(d) {}
    bool operator < (const Seg &cmp) const
    {
        return h<cmp.h;
    }
} e[N];
struct node
{
    int cnt;
    double len;
} t[N<<2];
double X[N];
void pushdown(int l,int r,int rt)
{
    if(t[rt].cnt)//当前的边被标记,就把当前的长度加上
        t[rt].len=X[r+1]-X[l];
    else if(l==r)//当为一个点的时候长度为0
        t[rt].len=0;
    else//其他情况把左右两个区间的值加上
        t[rt].len=t[rt<<1].len+t[rt<<1|1].len;/*根节点的cnt为0,并且l不等于r,说明根节点区间长度,代表矩形的长度是不连续的,中间有断开,只能看左子树,右子树的长度了。*/
}
/*update函数,L,R为当前边的左区间,和右区间,l,r为最大区间0,m,rt根节点 ,val接受是下边还是上边,不是1就是-1。*/
void update(int L,int R,int l,int r,int rt,int val)
{
    if(L<=l&&r<=R)
    {
        t[rt].cnt+=val;//加上标记的值
        pushdown(l,r,rt);//向下更新节点,把要更新左右区间和根节点作为参数
        return;
    }
    int m=(l+r)>>1;
    if(L<=m) update(L,R,lson,val);
    if(R>m) update(L,R,rson,val);
    pushdown(l,r,rt);
}
int main()
{
    int n,q=1;
    double a,b,c,d;
    while(~scanf("%d",&n)&&n)
    {
        mem(t,0);
        int num=0;
        for(int i=0; i<n; i++)
        {
            scanf("%lf%lf%lf%lf",&a,&b,&c,&d);
            X[num]=a;
            e[num++]=Seg(a,c,b,1);//矩形下面用1来标记吗
            X[num]=c;
            e[num++]=Seg(a,c,d,-1);//上面用-1来标记
        }
        sort(X,X+num);//用于离散化
        sort(e,e+num);//把矩形的边的纵坐标从小到大排序
        int m=unique(X,X+num)-X;
        double ans=0;
        for(int i=0; i<num; i++)
        {
            int l=lower_bound(X,X+m,e[i].l)-X;//找出离散化以后的值
            int r=lower_bound(X,X+m,e[i].r)-X-1;
            update(l,r,0,m,1,e[i].f);
            ans+=t[1].len*(e[i+1].h-e[i].h);
        }
        printf("Test case #%d\nTotal explored area: %.2lf\n\n",q++,ans);
    }
    return 0;
}
/*
2
10 10 20 20
15 15 25 25.5
0
*/

补充:有的学生看了我的博文还是不明白为什么使用线段树维护,我们把每个矩形的纵坐标排序,用水平扫描线把所有矩形组成的图形整体考虑,在垂直方向上分成高度固定,长度不同矩形,然后根据横坐标划分区间,用线段树维护区间,例如上面样例,横坐标是10 15 20 25,维护这三个区间,在扫描线向上扫的过程中,这三个区间在变化。
我们究竟怎么维护这三个区间呢?
通过上面我们每拿到一个矩形的数据,就保存矩形的上边和下边。用两个横坐标,一个纵坐标,一个标志就可以存储一条上边或下边。
下面代码是存储边的结构体:
struct Seg
{
double l,r,h;
int f;
Seg() {}
Seg(double a,double b,double c,int d):l(a),r(b),h©,f(d) {}
bool operator < (const Seg &cmp) const
{
return h<cmp.h;
}
} e[N];

下面代码把N个矩形转化为2N条边
for(int i=0; i<n; i++)
{
scanf(“%lf%lf%lf%lf”,&a,&b,&c,&d);
X[num]=a;
e[num++]=Seg(a,c,b,1);//矩形下面用1来标记吗
X[num]=c;
e[num++]=Seg(a,c,d,-1);//上面用-1来标记
}

下面代码至下而上扫描每一条边,每扫描一条边就用线段数组维护一下区间
for(int i=0; i<num; i++)
{
int l=lower_bound(X,X+m,e[i].l)-X;//找出离散化以后的值
int r=lower_bound(X,X+m,e[i].r)-X-1;
update(l,r,0,m,1,e[i].f);
ans+=t[1].len*(e[i+1].h-e[i].h);
}

线段树的数据结构

struct node
{
int cnt;
double len;
} t[N<<2];
我来解释一下线段树的结构:
cnt代表的矩形有几条入边 。
len计算的是区间长度。当cnt为0表示没有入边,也没有出边,表示在这段区间没有矩形。每当扫描到一条入边cnt+1,扫描到一条出边cnt-1。

综上所述:这题其实很简单,把矩形在高度上划分成长高度固定,长度在变的矩形。向上扫描,用线段树维护矩形长度的变化。O(N^2)算法参考博文Atlantis POJ1151(N^2算法)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值