码迷,mamicode.com
首页 > 其他好文 > 详细

线段树扫描线

时间:2020-03-22 01:21:27      阅读:56      评论:0      收藏:0      [点我收藏+]

标签:nod   相同   ring   操作   def   方法   面积   scanf   区间修改   

扫描线一般运用在图形上面,它和它的字面意思十分相似,就是一条线在整个图上扫来扫去,它一般被用来解决图形面积,周长等问题。     -------OI Wiki

扫描线求面积并

P5490 【模板】扫描线
技术图片
如图,假设三个矩形,求其并集的面积。考虑将每个矩形处理成两条平行于 y 轴的线段,左边的线段标记为 +1 ,右边的线段标记为 -1 ,用一个四元组\((x, y_1, y_2, k)\)存下来,于是我们得到了这样一个图形:
技术图片
按照四元组中 x 坐标由小到大排序,我们就可以分成一段一段地处理整个图形:
技术图片
不同颜色表示一次处理的一段,此图形共可分成5段。
考虑每次处理的矩形,沿 x 轴的长度很容易知道,即当前四元组 x 减去上一个四元组 x 。我们要处理沿 y 轴的长度,维护一个序列:
1.若当前读到一个标记为 +1 的线段 ,就在序列上将这段区间 +1 ,反之 - 1。
2.每次统计时求序列上标记大于 0 的长度即可。
具体处理:
1.由于题目中数据较大,需要离散化,用一个数组(num[])映射到原坐标值。
2.用线段树维护序列(序列中第 i 项对应\([num[i], num[i + 1] ]\)这个区间),线段树中每个节点维护两个值:s 和 cnt ,cnt 表示此段被标记的值,s 表示这段上 cnt > 0 的长度。update时,若当前结点 cnt > 0,那么 s 值为整段的长度,即\(num[r + 1] - num[l]\),否则为两个子结点长度之和,因为每次查询我们只关心整段序列,即线段树根结点,所以这个区间修改不需要标记下传,。
3.读入一个四元组\((x, y_1, y_2, k)\)时,先统计答案,再将序列上\([y_1, y_2 - 1]\)这段的标记加上 k ,。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long lld;
const int N = 100005;
const int M = 1000005;
int n, tot = 0, num[M], maxn = 0;
lld ans = 0;
struct Tree {
    int cnt, s;
} e[M << 2];
struct node {
    int x, l, r, k;
} p[N << 1];
void update(int i, int l, int r) {
    if(e[i].cnt > 0) e[i].s = num[r + 1] - num[l];// l, r表示num[l]到num[r + 1]这一段
    else e[i].s = e[i << 1].s + e[i << 1 | 1].s;
}
void add(int i, int l, int r, int nl, int nr, int k) {
    if(l >= nl && r <= nr) {
        e[i].cnt += k; update(i, l, r);
        return ;
    }
    int mid = (l + r) >> 1;
    if(nl <= mid) add(i << 1, l, mid, nl, nr, k);
    if(nr > mid) add(i << 1 | 1, mid + 1, r, nl, nr, k);
    update(i, l, r);
}
int get() {
    return e[1].s;
}
void add1(int x1, int x2, int y1, int y2) {
    p[++tot].x = x1; p[tot].l = y1; p[tot].r = y2; p[tot].k = 1;
    p[++tot].x = x2; p[tot].l = y1; p[tot].r = y2; p[tot].k = -1;
    maxn = max(y1, max(y2, maxn));
}
int a[N], b[N], c[N], d[N];
vector <int> tmp;
bool cmp(node a, node b) {
    return a.x < b.x;
}
int main() {
//  freopen("data.in", "r", stdin);
    scanf("%d", &n);
    for(int i = 1, x1, x2, y1, y2; i <= n; i++) {
        scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
        a[i] = x1, b[i] = x2, c[i] = y1, d[i] = y2;
        tmp.push_back(y1); tmp.push_back(y2);
    }
    sort(tmp.begin(), tmp.end());//离散化
    for(int i = 1; i <= n; i++) {
        int save1 = c[i], save2 = d[i];
        c[i] = lower_bound(tmp.begin(), tmp.end(), c[i]) - tmp.begin() + 1;
        d[i] = lower_bound(tmp.begin(), tmp.end(), d[i]) - tmp.begin() + 1;
        num[c[i]] = save1, num[d[i]] = save2;//映射原值
        add1(a[i], b[i], c[i], d[i]);//添加四元组
    }
    sort(p + 1, p + 1 + tot, cmp);//按照x排序
    add(1, 1, maxn, p[1].l, p[1].r - 1, p[1].k);//添加第一条线段
    for(int i = 2; i <= tot; i++) {
        ans = ans + (1ll * get() * (p[i].x - p[i - 1].x));//统计答案,长乘宽
        add(1, 1, maxn, p[i].l, p[i].r - 1, p[i].k);//修改序列
    }
    printf("%lld", ans);
    return 0;
}

扫描线求矩形周长

P1856 [USACO5.5]矩形周长Picture

方法类似求面积,离散化处理,线段树区间修改和查询方法同上。
统计答案: 读入一个四元组时,先记录当前的查询的值,进行修改后再查询一次,答案增加的量为两次查询的差的绝对值。 证明方法显然,这里不多bb。
细节:
1.这样每次只能统计平行于 y 轴的线段的周长和,我们需要将每个点的 x,y 坐标交换后再重复上述操作。
2.四元组排序时若 x 坐标相同,则 k = 1 的排在前面(先加上再减去对统计对答案没有影响,若是先减去在加上可能会导致答案偏大)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 5005;
const int M = 20015;
const int F = 10001;
int a[N], b[N], c[N], d[N], n, tot = 0;
int num[M << 2];
long long ans = 0;
struct node {
    int x, l, r, k;
} p[N << 1];
void add(int x1, int y1, int x2, int y2) {
    if(y1 > y2) swap(y1, y2);
    p[++tot].x = x1, p[tot].l = y1, p[tot].r = y2, p[tot].k = 1;
    p[++tot].x = x2, p[tot].l = y1, p[tot].r = y2, p[tot].k = -1;
}
struct tree {
    int s, cnt;
} e[M << 2];
vector <int> tmp, opt;
void update(int i, int l, int r) {
    if(e[i].cnt > 0) e[i].s = num[r + 1] - num[l];
    else e[i].s = e[i << 1].s + e[i << 1 | 1].s;
}
void add(int i, int l, int r, int nl, int nr, int k) {
    if(l >= nl && r <= nr) {
        e[i].cnt += k; update(i, l, r);
        return ;
    }
    int mid = (l + r) >> 1;
    if(nl <= mid) add(i << 1, l, mid, nl, nr, k);
    if(nr > mid) add(i << 1 | 1, mid + 1, r, nl, nr, k);
    update(i, l, r);
}
int get() {
    return e[1].s;
}
bool cmp(node a, node b) {
    if(a.x == b.x) return a.k > b.k; //k = 1排在前面
    return a.x < b.x;
}
void solve() {
    sort(p + 1, p + 1 + tot, cmp);
    for(int i = 1; i <= tot; i++) {
        long long per = get();
        add(1, 1, tot, p[i].l, p[i].r - 1, p[i].k);
        ans = (ans + abs(per - get()));
    }
}
int main() {
//  freopen("data.in", "r", stdin);
    scanf("%d", &n);
    for(int i = 1, x1, x2, y1, y2; i <= n; i++) {
        scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
        a[i] = x1, b[i] = y1, c[i] = x2, d[i] = y2;
        tmp.push_back(y1); tmp.push_back(y2);
        opt.push_back(x1), opt.push_back(x2);
    }
    sort(tmp.begin(), tmp.end());
    for(int i = 1; i <= n; i++) {
        int save1 = b[i], save2 = d[i];
        b[i] = lower_bound(tmp.begin(), tmp.end(), b[i]) - tmp.begin() + 1;
        d[i] = lower_bound(tmp.begin(), tmp.end(), d[i]) - tmp.begin() + 1;
        num[b[i]] = save1, num[d[i]] = save2;
        add(a[i], b[i], c[i], d[i]);
    }
    solve();
    for(int i = 0; i < (M << 2); i++) e[i].s = e[i].cnt = 0;
    memset(num, 0, sizeof(num));
    tot = 0;
    sort(opt.begin(), opt.end());
    for(int i = 1; i <= n; i++) {
        int save1 = a[i], save2 = c[i];
        a[i] = lower_bound(opt.begin(), opt.end(), a[i]) - opt.begin() + 1;
        c[i] = lower_bound(opt.begin(), opt.end(), c[i]) - opt.begin() + 1;
        num[a[i]] = save1, num[c[i]] = save2;
        add(b[i], a[i], d[i], c[i]);
    }
    solve();
    printf("%lld", ans);
    return 0;
}

线段树扫描线

标签:nod   相同   ring   操作   def   方法   面积   scanf   区间修改   

原文地址:https://www.cnblogs.com/mcggvc/p/12538960.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!