HDU1542Atlantis(线段树+离散化+扫描线)

题目传送门:http://acm.hdu.edu.cn/showproblem.php?pid=1542

首先附上大佬们的博客:

http://www.cnblogs.com/scau20110726/archive/2013/03/21/2972808.html

http://blog.csdn.net/xingyeyongheng/article/details/8927732#

题目大意:

求所有矩形覆盖的面积。

分析:

第一道关于扫描线的题目。首先解释一下扫描线是什么,就是一条你脑子中想象出来的一条虚拟的线。可以从下

往上扫(从上往下扫),也可以从左往右扫(从右往左扫),想怎么扫就得写什么样的代码。如果是竖直方向上扫描,

离散化横坐标,如果是水平方向上扫描,则是离散化纵坐标。下面的分析都是离散化横坐标的,并且从下往上扫描的。

其实第一次接触扫描线的题,问题肯定就是这个面积是怎么算的?一句话概括,就是这个矩形面积不是一个一个算的,

是每次所有矩形在某一层高度内的面积之和,然后计算每一层的。那层是什么东西?就是矩形上下边确定的高度。

扫描之前还需要一个工作,就是保存好所有矩形的上下边,并且按照他们的高度进行排序,另外如果是上边我们给

一个值-1, 下边给他一个值1,用一个结构体保存所有上下边。

struct segment
{
    double l, r, h;//l,r表示这条上下边的左右坐标,h是这条边所处的高度
    int f;//所赋的值,下边1,上边-1
}ss[2*maxn];

接着扫描线从下往上扫描,每遇到一条上下边就停下来,将这条线段投影到总区间上(总区间就是整个多边形横跨的长度),这个投影对应的其实是个插入和删除线段操作。还记得给他们赋的值1或-1吗,下边是1,扫描到下边的话相当于往总区间插入一条线段,上边-1,扫描到上边相当于在总区间删除一条线段(如果说插入删除比较抽象,那么就直白说,扫描到下边,投影到总区间,对应的那一段的值都要增1,扫描到上边对应的那一段的值都要减1,如果总区间某一段的值为0,说明其实没有线段覆盖到它,为正数则有,那会不会为负数呢?是不可能的,可以自己思考一下)。

每扫描到一条上下边后并投影到总区间后,就判断总区间现在被覆盖的总长度,然后用下一条边的高度减去当前这条边的高度,乘上总区间被覆盖的长度,就能得到一块面积,并依此做下去,就能得到最后的面积

(这个过程其实一点都不难,只是看文字较难体会,建议纸上画图,一画即可明白,下面献上一图希望有帮助)

从这个图,也可以感受到,就好比一个畸形的容器,往里面倒水,从最下面往上面涨,被水淹过的部分其实就是我们要求的面积

 

其实这个线段树就是以所有矩形上下边的左右端点建立的,维护的是一个区间的长度。

最后要注意一下一个很重要的技巧:

这里注意下扫描线段时r-1:int r = binary(ss[i].r, 0, m-1) - 1;

计算底边长时r+1:segTree[rt].len = pos[segTree[rt].r+1] - pos[segTree[rt].l];


解释:假设现在有一个线段左端点是l=0,右端点是r=m-1
则我们去更新的时候,会算到sum[1]=pos[mid]-pos[left]+pos[right]-pos[mid+1]
这样得到的底边长sum是错误的,why?因为少算了mid~mid+1的距离,由于我们这利用了
离散化且区间表示线段,所以mid~mid+1之间是有长度的,比如pos[3]=1.2,pos[4]=5.6,mid=3
所以这里用r-1,r+1就很好理解了 

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn = 110;
struct segment
{
    double l, r, h;//l,r表示这条上下边的左右坐标,h是这条边所处的高度
    int f;//-1为下边界,1为上边界
}ss[2*maxn];
struct Node//线段树节点
{
    int l, r, cnt;//cnt为该节点被覆盖的情况
    double len;//该区间被覆盖的总长度
}segTree[maxn<<3];
double pos[maxn*2];
int nums;
bool cmp(struct segment p, struct segment q)
{
    return p.h < q.h;
}
void build(int l, int r, int rt)
{
    segTree[rt].l = l;
    segTree[rt].r = r;
    segTree[rt].cnt = 0;
    segTree[rt].len = 0;
    if(l == r)
        return ;
    int mid = (segTree[rt].l + segTree[rt].r) >> 1;
    build(l, mid, rt<<1);
    build(mid+1, r, (rt<<1)|1);
}
void get_len(int rt)
{
    if(segTree[rt].cnt)//非0,已经被整段覆盖
        segTree[rt].len = pos[segTree[rt].r+1] - pos[segTree[rt].l];
    else if(segTree[rt].l == segTree[rt].r)//已经不是一条线段,只是一个点
        segTree[rt].len = 0;
    else//是一条线段但是又没有整段覆盖,那么只能从左右孩子的信息中获取
        segTree[rt].len = segTree[rt<<1].len + segTree[(rt<<1)|1].len;
}
void update(int l, int r, int val, int rt)
{
    if(segTree[rt].l == l && segTree[rt].r == r)//目标区间
    {
        segTree[rt].cnt += val;//更新这个区间被覆盖的情况
        get_len(rt);//更新这个区间被覆盖的总长度
        return ;
    }
    int mid = (segTree[rt].l + segTree[rt].r) >> 1;
    if(r <= mid)
        update(l, r, val, rt<<1);
    else if(l > mid)
        update(l, r, val, (rt<<1)|1);
    else
    {
        update(l, mid, val, rt<<1);
        update(mid+1, r, val, (rt<<1)|1);
    }
    get_len(rt);//计算该区间被覆盖的总长度
}
int binary(double key, int low, int high)
{
    int mid;
    while(low <= high)
    {
        mid = (low + high) >> 1;
        if(pos[mid] == key)
            return mid;
        else if(key < pos[mid])
            high = mid - 1;
        else
            low = mid + 1;
    }
    return -1;
}
int main()
{
    int Case = 0;
    int n;
    while(scanf("%d", &n) != EOF && n)
    {
        nums = 0;
        for(int i = 0; i < n; i++)
        {
            double x1, y1, x2, y2;
            scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
            ss[nums].l = x1;ss[nums].r = x2;ss[nums].h = y1;ss[nums].f = 1;//记录下边界的信息
            ss[nums+1].l = x1;ss[nums+1].r = x2;ss[nums+1].h = y2;ss[nums+1].f = -1;//记录上边界的信息
            pos[nums] = x1;pos[nums+1] = x2;//记录横坐标
            nums += 2;
        }
        sort(ss, ss+nums, cmp);//横线按纵坐标升序排序
        sort(pos, pos+nums);//横坐标升序排序
        int m = 1;
        for(int i = 1; i < nums; i++)
            if(pos[i] != pos[i-1])//去重
                pos[m++] = pos[i];
        build(0, m-1, 1);//离散化后的区间就是[0,m-1],以此建树
        double ans = 0;
        for(int i = 0; i < nums-1; i++)//拿出每条横线并且更新
        {
            int l = binary(ss[i].l, 0, m-1);
            int r = binary(ss[i].r, 0, m-1) - 1;
            update(l, r, ss[i].f, 1);//用这条线段去更新
            ans += (ss[i+1].h - ss[i].h) * segTree[1].len;
        }
        printf("Test case #%d\n", ++Case);
        printf("Total explored area: %.2f\n\n", ans);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值