POJ - 1177 Picture(线段树 扫描线 区间合并)

题目大意:给你N个矩形,要求你求出这N个矩形的轮廓线周长

解题思路:先离散化坐标,接着进行扫描线,我是离散x坐标的
先讨论一下,和x轴平行的轮廓线的周长,这里分两种情况的线
1.添加一条下线,如果总长度有增加的话,那么多出来的长度是属于轮廓线的一部分的,如果总长度没有增加,表示这条线被包含在内了,不属于轮廓线
2.添加一条上线,如果总长度有减少的话,那么减少的部分是属于轮廓线的,如果没有减少,表示该线被包含在内了,不属于轮廓线,这个可以画图观察

接下来要求的是和y坐标平行的轮廓线的周长了,和y坐标平行的轮廓线的周长就是找出所给线段的y坐标所连成的不相交的线段,有多少个,就表示有多少条和y坐标平行的轮廓线,然后乘上高度*2即可。
所以我们维护区间内的不相交的线段的数量sum,区间的最左端点是否被包括left,区间的最右端点是否被包括right
接下来进行的就是区间合并了,如果当前区间被cover了,那么sum = left = right = 1
如果没被cover,且l == r , sum = left = right = 0
如果没被cover且l != r,那sum[u] = sum[u << 1] + sum[u << 1 | 1],还得要判断一下,区间能否连续,如果能连续,那么sum[u]–,判断连续的话,就用right[u << 1]和left[u << 1 | 1],如果两个都是1,那么就是连续的了,并起来

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
using namespace std;
const int N = 10010 << 2;
struct Segment{
    int l, r, h, f;
}S[N];

int n, m, cnt;
int pos[N], cover[N], len[N], left[N], right[N], sum[N];

void build(int u, int l, int r) {
    len[u] = cover[u] = left[u] = right[u] = sum[u] = 0;
    if (l == r) return ;
    int mid = (l + r) >> 1;
    build(u << 1, l, mid);
    build(u << 1 | 1, mid + 1, r);
}

int cmp(const Segment a, const Segment b) {
    return a.h < b.h;
}

void init() {

    int x1, y1, x2, y2;
    cnt = 1;
    for (int i = 1; i <= n; i++) {
        scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
        pos[cnt] = S[cnt].l = S[cnt + 1].l = x1;
        pos[cnt + 1] = S[cnt].r = S[cnt + 1].r = x2;
        S[cnt].h = y1; S[cnt + 1].h = y2;
        S[cnt].f = 1; S[cnt + 1].f = -1;
        cnt += 2;
    }
    m = 1;
    sort(S + 1, S + cnt, cmp);
    sort(pos + 1, pos + cnt);
    for (int i = 2; i < cnt; i++)
        if (pos[i] != pos[i - 1]) pos[++m] = pos[i];
    build(1, 1, m);
}

void getlen(int u, int l, int r) {
    if (cover[u]) {
        len[u] = pos[r + 1] - pos[l];
        sum[u] = left[u] = right[u] = 1;
    }
    else if (l == r) len[u] = sum[u] = left[u] = right[u] = 0;
    else {
        len[u] = len[u << 1] + len[u << 1 | 1];
        sum[u] = sum[u << 1] + sum[u << 1 | 1];
        if (right[u << 1] && left[u << 1 | 1]) sum[u]--;
        left[u] = left[u << 1]; right[u] = right[u << 1 | 1];
    }

}

void Modify(int u, int l, int r, int L, int R, int c) {
    if (l == L && r == R) {
        cover[u] += c;
        getlen(u, l, r);
        return ;
    }
    int mid = (l + r) >> 1;
    if (R <= mid) Modify(u << 1, l, mid, L, R, c);
    else if (L > mid) Modify(u << 1 | 1, mid + 1, r, L, R, c);
    else {
        Modify(u << 1, l, mid, L, mid, c);
        Modify(u << 1 | 1, mid + 1, r, mid + 1, R, c);
    }
    getlen(u, l, r);
}

int find(int val) {
    int l = 1, r = m;
    while (l <= r) {
        int mid = (l + r) >> 1;
        if (pos[mid] == val) return mid;
        else if (pos[mid] < val) l = mid + 1;
        else r = mid - 1;
    }
    return -1;
}

void solve() {

    int ans = 0, last = 0;
    for (int i = 1; i < cnt; i++) {
        int l = find(S[i].l);
        int r = find(S[i].r) - 1;
        Modify(1, 1, m, l, r, S[i].f);
        if (i < cnt - 1) {
            ans += sum[1] * (S[i + 1].h - S[i].h) * 2;
            ans += abs(len[1] - last);
            last = len[1];
        }
        else ans += abs(len[1] - last);
    }
    printf("%d\n", ans);
}

int main() {
    while (scanf("%d", &n) != EOF) {
        init();
        solve();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值