扫描线小结

扫描线

1.算法分析

1.1 基本概念

对于很多问题来说,直接计算全局的答案十分困难,但是如果将整个问题(例如图形)分割为许多较小的区间,单独求解是较为简单的,扫描线算法就可以解决此类问题。

复杂度:

O ( n 2 l o g ( n ) ) O(n^2log(n)) O(n2log(n))

线段树优化后:

O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))

1.2 扫描线求矩形的面积并

图片.png

将每个矩形的左右边界保存并排序,从左到右扫描一遍,求条线之间的面积之并即可

暴力的方法是处理到每个小区间时,枚举每个矩形,看是否处于当前的区间之中,然后对于当前这个小区间计算面积的并集

线段树优化的方法是将矩形的左边界看做+1,右边界看做-1操作,每次扫描到之后,就进行区间修改,在修改之前,先看所有权值大于0的区间是多少,将其乘上两条线的距离就是这一段区间的贡献

图片.png

线段树维护:

1.cnt代表当前区间整个被覆盖次数

2.len表示不考虑祖先节点cnt的前提下,cnt大于0的区间总长

线段树节点永远只向下看不向上看

此题的两个性质(只有求矩形才能用,如果换成平行四边形就不行了):

1.永远只考虑根节点的信息:query的时候不需要pushdown

2.所有操作都是成对出现且先加后减:modify的时候不需要pushdown

1.3 扫描线求三角形的面积并

不仅需要考虑顶点,还需要考虑每条线的交点:

6T4ZCt.png

这样分割出来的区间一定没有交点,且面积都被分成了多个连续的矩形:

6T4aKU.png

所以求每个区间的矩形面积即可,复杂度为 O ( n 3 l o g ( n ) ) O(n^3log(n)) O(n3log(n)),因为每两个三角形都有可能有交点,所以比矩形的面积并多了一个n的复杂度

1.4 扫描线求矩形面积的交

这里讲解线段树优化的做法,只需要在求矩形面积并的基础上加以修改即可

我们要计算被覆盖两次或以上的部分面积,我们在线段树节点中增设了一个变量,more,其中len表示该该区间内被覆盖了1次或以上的长度,more表示被覆盖了2次或以上的长度

怎么计算最后的面积的?一样的道理,从左到右扫描矩形,然后看看t[1].more是多少,再乘上高度差。因为t[1]表示了总区间,而more表示被覆盖两次或以上的长度,即计算时我们忽略掉只被覆盖一次的长度

问题的关键变为怎么计算一个节点的more

分情况讨论

1.cnt>1 : 说明该区间被覆盖两次或以上,那么长度就可以直接计算,就是该区间的长度

剩下的情况就是cnt=1或cnt=0

2.先看叶子节点,因为是叶子没有孩子了,所以被覆盖两次或以上的长度就是0(无论cnt=1或cnt=0都是0,因为是叶子。。。)

3.不是叶子节点 ,且cnt=1.注意这里,cnt=1确切的意义是什么,应该是:可以确定这个区间被完全覆盖了1次,而有没有被完全覆盖两次或以上则不知道无法确定,那么怎么办呢,只要加上len[lch] + len[rch] 即,看看左右孩子区间被覆盖了一次或以上的长度,那么叠加在双亲上就是双亲被覆盖两次或以上的长度

3.不是叶子节点,且cnt=0,确切的意义应该是不完全不知道被覆盖的情况(不知道有没有被覆盖,被覆盖了几次,长度是多少都不知道),这种情况,只能由其左右孩子的信息所得:more[lch] + more[rch] , 即直接将左右孩子给覆盖了两次或以上的长度加起来,这样才能做到不重不漏

1.5 扫描线求矩形周长的并

和线段树求矩形周长的并类似,不过不需要乘高,需要注意的是,每次计算的是len[1]与上一次的len[1]的差值:

这里写图片描述

但是这样只是求了横着的周长,所以分别做两次扫描线,求出x和y方向上的周长即可。

2.模板

2.1 求矩形的面积并

扫描线:

#include <bits/stdc++.h>

#define x first
#define y second

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;
const int N = 1010;

int n;
PII l[N], r[N];
PII q[N];

LL range_area(int a, int b) {
   
    int cnt = 0;
    for (int i = 0; i < n; i++)
        if (l[i].x <= a && r[i].x >= b) q[cnt++] = {
   l[i].y, r[i].y};
    if (!cnt) return 0;
    sort(q, q + cnt);
    LL res = 0;
    int st = q[0].x, ed = q[0].y;
    //区间合并
    for (int i = 1; i < cnt; i++)
        if (q[i].x <= ed)
            ed = max(ed, q[i].y);
        else {
   
            res += ed - st;
            st = q[i].x, ed = q[i].y;
        }
    res += ed - st;
    return res * (b - a);
}

int main() {
   
    scanf("%d", &n);
    vector<int> xs;
    for (int i = 0; i < n; i++) {
   
        scanf("%d%d%d%d", &l[i].x, &l[i].y, &r[i].x, &r[i].y);
        xs.push_back(l[i].x), xs.push_back(r[i].x);
    }
    sort(xs.begin(), xs.end());
    LL res = 0;
    for (int i = 0; i + 1 < xs.size(); i++)
        //计算两条线之间的小矩形面积
        if (xs[i] != xs[i + 1]) res += range_area(xs[i], xs[i + 1]);
    printf("%lld\n", res);
    return 0;
}

扫描线+线段树:

#include <bits/stdc++.h>

using namespace std;

const int N = 100010;

int n;
struct Segment {
   
    double x, y1, y2;
    int k;
    bool operator<(const Segment &t) const {
    return x < t.x; }
} seg[N * 2];
int cnt[N << 3];  // cnt[u]维护u的管理区间是否全部都被覆盖
double len[N << 3];  // len[u]维护u的管理区间内覆盖的长度

vector<double> ys;

int find(double y) {
    return lower_bound(ys.begin(), ys.end(), y) - ys.begin(); }

void pushup(int u, int l, int r) {
   
    if (cnt[u])  // 如果这段都被覆盖,那么有效的长度就是这段长度
        len[u] = ys[r + 1] - ys[l];
    else if (l != r)  // 如果这段没有完全覆盖,那么就是左子区间被覆盖长度+右子区间被覆盖长度
        len[u] = len[u << 1] + len[u << 1 | 1];
    else  // 要不然就是0
        len[u] = 0;
}

void build(int u, int l, int r) {
   
    len[u] = cnt[u] = 0;
    if (l != r) {
   
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
    }
}

void modify(int u, int l, int r, int L, int R, int k) {
   
    if (L <= l && r <= R) {
   
        cnt[u] += k;
        pushup(u, l, r);
    } else {
   
        int mid = l + r >> 1;
        if (L <= mid) modify(u << 1, l, mid, L, R, k);
        if (mid < R) modify(u << 1 | 1, mid + 1, r, L, R, k);
        pushup(u, l, r);
    }
}

int main() {
   
    int T = 1;
    while (scanf("%d", &n) != EOF) {
   
        if (!n) break;
        ys.clear();
        for (int i = 0, j = 0; i < n; i++) {
   
            double x1, y1, x2, y2;
            scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
            seg[j++] = {
   x1, y1, y2, 1};  // 矩形的左边界+1
            seg[j++] = {
   x2, y1, y2, -1};  // 右边界减1
            ys.push_back(y1), ys.push_back(y2);  // y离散化
        }

        // 排序去重
        sort(ys.begin(), ys.end());
        ys.erase(unique(ys.begin(), ys.end()), ys.end());

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值