HDU - 1542 求矩形面积并(线段树+离散化+扫描线)

There are several ancient Greek texts that contain descriptions of the fabled island Atlantis. Some of these texts even include maps of parts of the island. But unfortunately, these maps describe different regions of Atlantis. Your friend Bill has to know the total area for which maps exist. You (unwisely) volunteered to write a program that calculates this quantity. 

Input

The input file consists of several test cases. Each test case starts with a line containing a single integer n (1<=n<=100) of available maps. The n following lines describe one map each. Each of these lines contains four numbers x1;y1;x2;y2 (0<=x1<x2<=100000;0<=y1<y2<=100000), not necessarily integers. The values (x1; y1) and (x2;y2) are the coordinates of the top-left resp. bottom-right corner of the mapped area. 

The input file is terminated by a line containing a single 0. Don’t process it.

Output

For each test case, your program should output one section. The first line of each section must be “Test case #k”, where k is the number of the test case (starting with 1). The second one must be “Total explored area: a”, where a is the total explored area (i.e. the area of the union of all rectangles in this test case), printed exact to two digits to the right of the decimal point. 

Output a blank line after each test case. 

Sample Input

2
10 10 20 20
15 15 25 25.5
0

Sample Output

Test case #1
Total explored area: 180.00 

 题意:

以左,下坐标和右上坐标的形式给定n个矩形,求这些矩形的面积并。

容斥大法好,但一定会超时。(2^100)

所以我们引入“扫描线”算法

我们来看这样的一张图

扫描线1

简化后,我们要计算的其实就是下图的面积:

扫描线2

如果我们用一条水平的直线,从下往上扫过整个坐标系,那么对于这条水平的直线,在扫的过程中,由于纵坐标是确定的,每个矩形就会在这条直线上形成投影,这条直线上就会有一段被所有矩形的并集覆盖的区间。

所以整个图形的并,就可以被分为2*n段,每一段在直线上覆盖的线段长短都是固定的,故其对应的面积就是覆盖区间长度*对应两条水平线的高度差

我们只关心矩形在水平直线上的投影长度,故而,我们可以把图形再做一次简化,只留下矩形水平的边

扫描线3

现在,我们只要记录这些水平边就可以了。

考虑一个这样的结构体,l代表线段的左端点,r代表线段的右端点,h代表线段的高度,也就是矩形和条水平边的纵坐标。

特殊之处在与引入变量f记录下边还是上边,下边记为1,上边记为-1。

因为我们的扫描线是自下而上进行扫描,所以就可以先根据h对line数组进行排序。而当扫到一条下边,我们可以用此边对区间进行标记,表示现在的下边区间[l,r]会被一条新的边覆盖;如果扫到一条上边,则说明现在的上边区间有一条原来覆盖这个区间的边要被舍弃。

struct node1
{
    double l,r;
    double h;
    int f;
}line[maxn];

现在,我们成功把问题转化成了区间并的问题,这是可以用线段树解决的,但是问题在于,由于题目中给的坐标不是整数,简单的线段树的l,r区间就无法表示我们理想的区间了,所以我们可以对所有横坐标先进行一次离散化处理,离散化后的lsh个点,把整个区间分成了lsh-1个小区间。

离散化处理后,就可以定义线段树了。

这时,线段树的l,r不是简单的[l,r]区间了,而对应的是上面离散化后的x[l]和x[r]所对应的区间;而cnt代表的是目前这个大区间被覆盖过了几次len代表此区间在扫描线上的投影长度

显然,如果cnt大于0,那么该区间的在扫描线上的投影长度就是现在的这个区间所对应的长度;如果cnt等于0,则说明这个区间并没有被完整覆盖,此时应该继续检查它的左子区间和右子区间

P.S.由于自下而上进行扫描,那么区间的cnt值一定是先扫到下边+1,后扫到上边-1,不可能存在负数。

struct node2
{
    int l,r;
    int cnt;
    double len;
}tr[maxn];

而真正去写时,用到了一个非常重要的技巧

因为我们的l和r是离散化后的横坐标,所以无法处理[x,x+1]这种问题,所以巧妙地将线段树的区间定义为[l,r),这样的话,tr[pos].len = x[tr[pos].r+1]-x[tr[pos].l],而在二分查找某横坐标在离散化后的x中对应的下标,则可以将查找出来的r-1,这样得出的就是正确区间了,int r = lower_bound(x+1,x+1+lsh,line[i].r)-x-1。

#include <cstdio>
#include <stack>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
#include <map>
#include <vector>
#include <queue>
#include <set>
#define eps 1e-8
#define mmset(arr,x) memset(arr,x,sizeof arr)
#define lson l,mid,pos<<1
#define rson mid+1,r,pos<<1|1
typedef long long ll;
const double PI = acos(-1.0);
const int maxn = 4e3;
const int INF = 1e9;
const ll linf = 0x3f3f3f3f3f3f3f3f;
using namespace std;
int n;
//装载离散化后的横坐标
double x[maxn];
//放线段
struct node1
{
    double l,r;
    double h;
    int f;
}line[maxn];
int cmp(node1 a, node1 b)
{
    return a.h<b.h;
}
struct node2
{
    int l,r;
    int cnt;
    double len;
}tr[maxn];
void debug(int num)
{
    cout<<"ok"<<num<<endl;
}
int findPos(int l, int r, double val)
{
    int mid;
    while(l<=r)
    {
        mid = (l+r)>>1;
        if(x[mid]>val)
            r = mid-1;
        else if(x[mid]<val)
            l = mid+1;
        else
            break;
    }
    return mid;
}
void build(int pos,int l,int r)
{
    tr[pos].l = l;
    tr[pos].r = r;
    tr[pos].len = 0;
    tr[pos].cnt = 0;
    if(l == r)
        return;
    int mid = (l+r)>>1;
    build(pos<<1,l,mid);
    build(pos<<1|1,mid+1,r);
}
void pushup(int pos)
{
    //debug(5);
    if(tr[pos].cnt)
        tr[pos].len = x[tr[pos].r+1]-x[tr[pos].l];
    else if(tr[pos].l == tr[pos].r)
        tr[pos].len = 0;
    else
        tr[pos].len = tr[pos<<1].len+tr[pos<<1|1].len;
}
void update(int pos,int l,int r,int val)
{
    //debug(4);
    if(tr[pos].l>=l && tr[pos].r<=r)
    {
        tr[pos].cnt += val;
        pushup(pos);
        return;
    }
    int mid = (tr[pos].l+tr[pos].r)>>1;
    if(l<=mid)
        update(pos<<1,l,r,val);
    if(r>mid)
        update(pos<<1|1,l,r,val);
    pushup(pos);
}

int main()
{
    int tt = 1;
    while(~scanf("%d",&n) && n)
    {
        int cnt = 0;
        for(int i = 1;i<=n; i++)
        {
            double x1,y1,x2,y2;
            scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
            x[++cnt] = x1;
            line[cnt].l = x1;
            line[cnt].r = x2;
            line[cnt].h = y1;
            line[cnt].f = 1;
            x[++cnt] = x2;
            line[cnt].l = x1;
            line[cnt].r = x2;
            line[cnt].h = y2;
            line[cnt].f = -1;
        }
        //debug(1);
        sort(x+1,x+cnt+1);
        sort(line+1,line+cnt+1,cmp);
        int lsh = unique(x+1,x+cnt+1)-x-1;
        build(1,1,cnt);
        //debug(2);
        double ans = 0;
        for(int i = 1; i<=cnt; i++)
        {
            int l = lower_bound(x+1,x+1+lsh,line[i].l)-x;
            int r = lower_bound(x+1,x+1+lsh,line[i].r)-x-1;
            update(1,l,r,line[i].f);
            //debug(3);
            ans += tr[1].len*(line[i+1].h-line[i].h);
        }
        printf("Test case #%d\n",tt++);
        printf("Total explored area: %.2f\n\n",ans);


    }
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值