ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

扫描线

2022-04-05 19:01:50  阅读:179  来源: 互联网

标签:int 线段 tree 扫描线 x2 include


简介

扫描线一般运用在图形上面,顾名思义,就是一条线在整个图上扫来扫去,它一般被用来解决图形面积,周长等问题。

扫描线的主要思想是虚拟出一条平行与 x 或 y 轴的无限长的线一路扫过去,如果发现触碰到矩形的边(被当前扫描线完全覆盖)就停下进行相关操作。

一、面积并

题型:在二维坐标系上,给出多个矩形的左下以及右上坐标,求出所有矩形构成的图形的面积。

考虑一个这样的例子

假设一个自下而上的扫描线,每次会在碰到横边的时候停下来

为了快速计算出截线段长度,可以将横边赋上不同的权值,具体为:对于一个矩形,其下边权值为 1,上边权值为 −1

然后把所有的横边按照 y 坐标升序排序。这样,对于每个矩形,扫描线总是会先碰到下边,然后再碰到上边。那么就能保证扫描线所截的长度永远非负

这样操作以后,就可以和线段树扯上关系。先把所有端点在 x 轴上离散化(其实就是把所有点的横坐标存到 X 数组里,然后排个序并且去重

在这个例子中,4 个端点的纵投影总共把 x 轴切割成了 5 部分。取中间的 3 部分线段,建立一棵线段树,其每个端点维护一条线段(也就是一个区间)的信息,之后整个问题就转化为了一个区间查询问题,即每次将当前扫描线扫到的边对应的信息按照之前赋上的权值更新,然后再查询线段树根节点的信息,最后得到当前扫描线扫过的面积

一些具体实现

对于线段树,我们大部分情况维护一个 sum 为当前区间被几个矩形覆盖,以及一个 len 表示当前区间被覆盖的区间长度

建树

void build(int i, int l, int r) {
  tree[i].l = l; tree[i].r = r;
  tree[i].len = tree[i].sum = 0;
  if (l == r) return;
  int mid = (l + r) >> 1;
  build(i << 1, l, mid);
  build(i << 1 | 1, mid + 1, r);
  return;
} 

pushup

我们扫到一条线,就对这个区间的 sum 进行修改,再由 cnt 对 len 修改,并且把 len 向上更新

inline void pushup(int i) {
  int l = tree[i].l, r = tree[i].r;
  if (tree[i].sum) tree[i].len = x[r + 1] - x[l]; //被覆盖过则更新长度
  else tree[i].len = tree[i << 1].len + tree[i << 1 | 1].len; //合并儿子信息
}

在 pushup 函数中我们对于被完全覆盖的区间,是用了其区间右端点 +1 进行计算。这是因为在线段树的叶节点上,线段树上的区间是 [l, l] 但是这样的区间在几何中长度为 0 了,所以我们令区间 [l, r] 表示的区间实际为 [x[l], x[r+1]] (x 是离散化数组) 就可以解决这个问题

例题

P5490 【模板】扫描线

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 10;
int n;
long long x1, yy1, x2, y2, ans;
long long x[maxn << 1];
struct scanline{
  long long l, r, h;
  int flag;
  bool operator < (const scanline &num) const {
    return h < num.h;
  }
}line[maxn << 1];
struct node{
  int l, r, sum;
  long long len;
}tree[maxn << 2];
void build(int i, int l, int r) {
  tree[i].l = l; tree[i].r = r;
  tree[i].len = tree[i].sum = 0;
  if (l == r) return;
  int mid = (l + r) >> 1;
  build(i << 1, l, mid);
  build(i << 1 | 1, mid + 1, r);
  return;
} 
inline void pushup(int i) {
  int l = tree[i].l, r = tree[i].r;
  if (tree[i].sum) tree[i].len = x[r + 1] - x[l];
  else tree[i].len = tree[i << 1].len + tree[i << 1 | 1].len;
}
void update(int i, long long L, long long R, int num) {
  int l = tree[i].l, r = tree[i].r;
  //注意: l、r和L、R的意义完全不同。l、r表示这个节点管辖的下标范围,而L、R则表示需要修改的真实区间
  if (x[r + 1] <= L || x[l] >= R) return;
  if (L <= x[l] && x[r + 1] <= R) {
    tree[i].sum += num;
    pushup(i);
    return;
  }
  update(i << 1, L, R, num);
  update(i << 1 | 1, L, R, num);
  pushup(i);
}
signed main() {
  cin >> n;
  for (int i = 1; i <= n; i++) {
    scanf("%lld%lld%lld%lld", &x1, &yy1, &x2, &y2);
    x[2 * i - 1] = x1; x[2 * i] = x2;
    line[2 * i - 1] = (scanline){x1, x2, yy1, 1};
    line[2 * i] = (scanline){x1, x2, y2, -1};
  }
  n <<= 1;
  sort(x + 1, x + 1 + n);
  sort(line + 1, line + 1 + n);
  int tot = unique(x + 1, x + 1 + n) - (x + 1);
  build(1, 1, tot - 1);
  //[1, tot - 1]描述的就是[X[1], X[tot]]
  for (int i = 1; i < n; i++) {
    update(1, line[i].l, line[i].r, line[i].flag);
    ans += tree[1].len * (line[i + 1].h - line[i].h);
  }
  printf("%lld\n", ans);
  return 0;
}

POJ-1151 Atlantis

#include <iostream>
#include <cmath>
#include <cstring>
#include <cstdio>
#include <algorithm> 
using namespace std;
const int maxn = 1e6 + 10;
int n, cnt;
double x1, yy1, x2, y2, ans;
double x[maxn << 1];
struct scanline{
  double l, r, h;
  int flag;
  bool operator < (const scanline &num) const {
    return h < num.h;
  }
}line[maxn << 1];
struct node{
  int l, r, sum;
  double len;
}tree[maxn << 2];
inline void pushup(int i) {
  int l = tree[i].l, r = tree[i].r;
  if (tree[i].sum) tree[i].len = x[r + 1] - x[l];
  else tree[i].len = tree[i << 1].len + tree[i << 1 | 1].len;
}
void build(int i, int l, int r) {
  tree[i].l = l; tree[i].r= r;
  tree[i].len = tree[i].sum = 0;
  if (l == r) return;
  int mid = (l + r) >> 1;
  build(i << 1, l, mid);
  build(i << 1 | 1, mid + 1, r);
  pushup(i);
  return;
}
void update(int i, double L, double R, int num) {
  int l = tree[i].l, r = tree[i].r;
  if (x[r + 1] <= L || x[l] >= R) return;
  if (L <= x[l] && x[r + 1] <= R) {
    tree[i].sum += num;
    pushup(i);
    return;
  }
  update(i << 1, L, R, num);
  update(i << 1 | 1, L, R, num);
  pushup(i);
}
signed main() {
  while (cin >> n && n) {
    ans = 0; cnt++;
    for (int i = 1; i <= n; i++) {
      cin >> x1 >> yy1 >> x2 >> y2;
      x[2 * i - 1] = x1; x[2 * i] = x2;
      line[2 * i - 1] = (scanline){x1, x2, yy1, 1};
      line[2 * i] = (scanline){x1, x2, y2, -1};
    }
    n <<= 1;
    sort(x + 1, x + 1 + n);
    sort(line + 1, line + 1 + n);
    int tot = unique(x + 1, x + 1 + n) - (x + 1);
    build(1, 1, tot - 1);
    for (int i = 1; i < n; i++) {
      update(1, line[i].l, line[i].r, line[i].flag);
      ans += tree[1].len * (line[i + 1].h - line[i].h); 
    }
    printf("Test case #%d\n", cnt);
    printf("Total explored area: %.2f\n\n", ans);
  }
  return 0;
}

二、周长并

P1856 [IOI1998] [USACO5.5] 矩形周长Picture 为例,假使有如下几条扫描线,考虑所截得的竖边长

发现:纵边总长度 = ∑ (2 × 被截得的线段条数 × 扫过的高度)

发现:横边总长度 = ∑∣上次截得的总长 − 现在截得的总长∣

所以和面积并比起来,周长并中的线段树还要维护线段的条数。另外,横边和纵边需分别计算

具体表现为:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e3 + 10;
int n, x1, yy1, x2, y2, ans, pre;
int x[maxn << 1];
struct scanline{
  int l, r, h, flag;
  bool operator < (const scanline &num) const {
    if (h == num.h) return flag > num.flag;
    return h < num.h;
  }
}line[maxn << 1];
struct node{
  int l, r, sum, len, cnt;
  //cnt表示区间线段条数
  bool lc, rc;
}tree[maxn << 2];
void build(int i, int l, int r) {
  tree[i].l = l; tree[i].r = r;
  tree[i].lc = tree[i].rc = 0;
  tree[i].len = tree[i].sum = tree[i].cnt = 0;
  if (l == r) return;
  int mid = (l + r) >> 1;
  build(i << 1, l, mid);
  build(i << 1 | 1, mid + 1, r);
  return;
} 
inline void pushup(int i) {
  int l = tree[i].l, r = tree[i].r;
  if (tree[i].sum) {
    tree[i].len = x[r + 1] - x[l];
    tree[i].lc = tree[i].rc = true;
    tree[i].cnt = 1; //做好相应的标记
  }
  else {
    tree[i].len = tree[i << 1].len + tree[i << 1 | 1].len;
    tree[i].lc = tree[i << 1].lc;
    tree[i].rc = tree[i << 1 | 1].rc;
    tree[i].cnt = tree[i << 1].cnt + tree[i << 1 | 1].cnt;
    //如果左儿子右端点和右儿子左端点都被覆盖,那么中间就是连续的一段,所以要-1
    if (tree[i << 1].rc && tree[i << 1 | 1].lc) tree[i].cnt--;
  }
}
void update(int i, long long L, long long R, int num) {
  int l = tree[i].l, r = tree[i].r;
  if (x[r + 1] <= L || x[l] >= R) return;
  if (L <= x[l] && x[r + 1] <= R) {
    tree[i].sum += num;
    pushup(i);
    return;
  }
  update(i << 1, L, R, num);
  update(i << 1 | 1, L, R, num);
  pushup(i);
}
signed main() {
  cin >> n;
  for (int i = 1; i <= n; i++) {
    scanf("%d%d%d%d", &x1, &yy1, &x2, &y2);
    x[2 * i - 1] = x1; x[2 * i] = x2;
    line[2 * i - 1] = (scanline){x1, x2, yy1, 1};
    line[2 * i] = (scanline){x1, x2, y2, -1};
  }
  n <<= 1;
  sort(x + 1, x + 1 + n);
  sort(line + 1, line + 1 + n);
  int tot = unique(x + 1, x + 1 + n) - (x + 1);
  build(1, 1, tot - 1);
  for (int i = 1; i < n; i++) {
    update(1, line[i].l, line[i].r, line[i].flag);
    ans += abs(pre - tree[1].len);
    pre = tree[1].len;
    ans += 2 * tree[1].cnt * (line[i + 1].h - line[i].h);
  }
  ans += line[n].r - line[n].l; //特判一下枚举不到的最后一条扫描线
  printf("%d\n", ans);
  return 0;
}

参考:
【学习笔记】扫描线
cbdsopa-扫描线

标签:int,线段,tree,扫描线,x2,include
来源: https://www.cnblogs.com/Moomin/p/16101079.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有