线段树常用案例2——矩形面积并

本篇博客在上一篇博客基础上讲解,链接:线段树

引例

来看一道题目:Atlantis

这道题的题意是给定若干个平行于坐标轴的矩形,求出这若干个矩形的面积之和,如果有重合的部分,只算一次。

首先考虑暴力的算法,即遍历每个矩形,用一个vis数组记录每个点的访问状态,最后可以统计出总的面积,这种方法在数据小且边长为整数的时候适用。

这类问题应该引入扫描线来做,下面来讲讲扫描线的概念。


扫描线在并行面积并中的应用

首先得定义扫描线 ,扫描线就是一条设想的线,它可以是从水平方向上下扫描,也可以从竖直方向左右扫描,对所有的矩形进行遍历,计算面积,两个方向的效果都是一样的,本文下面所讲都是前者的扫描方法。

如下图1.1所示,数字1所标识的是两个矩形,数字2所标识的是经过扫描线的扫描,将所有的矩形分成若干个部分来分别计算。虚线就代表扫描线,蓝色箭头方向表示扫描的方向,我们可以发现,矩形被分割成几个部分,是取决于矩形的上下边的 ,每遇到一条上边或者下边,就分割出了一个部分。

图1.1

明白了扫描线的基本概念之后,我们再来讨论,再将所有矩形划分成若干个部分后,到底应该如何计算每个部分的面积。

显然如果我们将扫描线以从下向上的方式来进行扫描(如上图),那么我们需要记录每条水平的边,这样以扫描线的视角来扫描每条水平边的时候,不难想到 ,如果我们记录了每条水平边的长度和竖直方向的高之后,我们可以很容易的计算每个部分的高了。

图1.2

如上图1.2所示,如果我们记录了红色部分的下边H= 10, 上边 H= 20,那么我们将扫描线从1扫描到2的时候,显然红色部分的高可以由两者之差轻易取得。

现在关键在于扫描每个部分之后,我们应该如何求出该部分的长度,这里就用线段树来维护每个部分的长度,下面讲讲如何实现

为了实现每次都能动态的得到每个部分的长度,我们需要将每个矩形的下边记为1,上边记为-1。因为我们实际上扫描到一个矩形的下边的时候,我们已经计算了这个部分的面积,所以再次扫描到这个矩形的上边的时候,我们只需要撤销这个部分的长度即可而记录或者撤销某个部分的长度,在线段树中的操作就是统计那个区间的长度,我们将某个区间的每个点加1,就是记录了该区间的长度,而将某个区间的每个点减1,就是撤销了该区间的长度。

此外,我们还得注意, 由于矩形的长度可以很大,而且可以为浮点数,因此我们需要离散化长度的数据,这样才能更便于线段树的构造。

所谓的离散化,就是将一些数据做一下映射(个人理解),使之变得易于处理 。

如下图1.3所示,我们可以看到矩形的长度有浮点数,所以我们可以将坐标10映射为下标1,坐标15映射为下标2,坐标20映射为下标3,坐标25.5映射为下标4,然后将所有坐标记录在一个数组中,这样我们可以只用下标构造线段树,需要用到实际的值的时候,用下标在数组中查询即可。

图1.3

 完整代码

 贴出引例中的完整代码 ,这里需要注意我们在求每条水平边左右点的low_bound的时候,边的右点必须先减1,最后在线段树中再加回1。用上图1.3来说明, 我们扫描第一条水平边的时候,如果用区间[1,3]记录这条边的长度,那么显然在线段树中它会被变成[1,2]和[3,3]来分别记录,但[3,3]会被记录成0,因此最后边记录就会出现问题。如果我们开始减 1,用区间[1,2]来记录,就不会出现这样的问题 

 

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#define maxn 1000010
using namespace std;

double x[maxn<<4];

struct no{
  double l,r;
  double h;
  int f;
}s[maxn<<4];

struct node{
  int l,r;
  int add;
  double sum;
}T[maxn<<4];

bool cmp(no &n1, no &n2)
{
  return n1.h < n2.h;
}

void build(int left, int right, int num)
{
  T[num].l = left;
  T[num].r = right;
  T[num].add = T[num].sum = 0;
  if(left == right) return;

  int mid = (left+right) / 2;
  build(left, mid, 2*num);
  build(mid+1, right, 2*num+1);
}

void pushup(int num)
{
  if(T[num].add)
    T[num].sum = x[T[num].r+1] - x[T[num].l];
  else if(T[num].l == T[num].r)
    T[num].sum = 0;
  else
    T[num].sum = T[2*num].sum + T[2*num+1].sum;
}

void update(int x, int y, int c, int num)
{
  if(x == T[num].l && T[num].r == y)
  {
    T[num].add += c;
    pushup(num);
    return;
  }
  int mid = (T[num].l + T[num].r)/2;
  if(x > mid) update(x, y, c, 2*num+1);
  else if(y <= mid) update(x, y, c, 2*num);
  else
  {
    update(x, mid, c, 2*num);
    update(mid+1, y, c, 2*num+1);
  }
  pushup(num);
}
int main()
{

  int Count = 1;
  int m;
  int n;
  double x1, y1, x2, y2;
  while(~scanf("%d", &n) && n)
  {
    double ans = 0;
    int m = 0;
    for(int i = 1; i <= n; i++)
    {
      scanf("%lf%lf%lf%lf",&x1, &y1, &x2, &y2);
      x[++m] = x1;
      s[m] = (no){x1,x2,y1,1};
      x[++m] = x2;
      s[m] = (no){x1,x2,y2,-1};
    }

    sort(x+1, x+m+1); // 横坐标排序
    sort(s+1, s+m+1, cmp); // 上下边排序 1表示下边

    int k = unique(x+1, x+m+1) - x; // 返回不同元素的后一个地址值
    build(1, k-1, 1);
 
    for(int i = 1; i < m; i++)
    {
      int l = lower_bound(x+1, x+k, s[i].l) - x; //返回大于或等于val的第一个元素位置
      int r = lower_bound(x+1, x+k, s[i].r) - x - 1;
      update(l, r, s[i].f, 1);
      ans += T[1].sum * (s[i+1].h - s[i].h);

    }

    printf("Test case #%d\nTotal explored area: %.2f\n\n", Count++, ans);
  }

  return 0;
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值