[lnsyoj283/luoguP1856/IOI1998]矩形周长Picture

题意

原题链接
求几个矩形的周长并

sol

遇到几何图形的**并,都可以使用扫描线思想来解决
观察易得,与x轴平行的边和与y轴平行的边相互独立,因此可以扫描两次,分别计算并累加
以与x轴平行的边为例:
假设有一条平行于x轴的直线从下到上扫描,每当遇到一条边时,若这条边是某个矩形的下边,则在直线上覆盖这条边,否则就在直线上清除掉对应下边的覆盖标记。我们设这是第\(i\)条边,在直线上有\(len_i\)单位长度被覆盖,则第\(i\)条边的贡献为\(|len_i - len_{i-1}|\),特别地,\(len_0 = 0\)

证明

当遇到一条边时,可能会有两种情况:

  1. 这条边是下边,则这条边中处于矩形并的部分不会计算贡献,而不位于矩形并中的部分的贡献为\(len_i - len_{i-1}\)
  2. 这条边是上边,则这条边处于除该矩形的所有矩形的并的部分不会计算贡献,而不位于该并的部分的贡献为\(len_{i-1} - len_i\)

综上,该边的贡献为\(|len_i - len_{i-1}|\)


因此,我们只需要处理以下三个操作:

  1. 在直线上的一个区间覆盖标记
  2. 在直线上删除一个区间的覆盖标记
  3. 查询被覆盖的单位长度

对于本题来说,由于数据较弱,因此这三个操作即便是\(O(n)\)的暴力也不会TLE,但对本题来说,仍可以使用线段树来优化到\(O(n\log n)\)
我们可以在线段树上记录两个变量:\(cnt\)表示该区间被覆盖的次数,\(val\)表示该区间被覆盖的单位长度。在UPDATE时,我们可以将边对应的值直接加到\(cnt\)中,若是矩形下边,该值就为\(1\),上边则为\(0\)。然后,若这个区间被整体覆盖过,即\(cnt \le 1\),则\(val\)就为区间长度,否则\(val\)为两个儿子的\(val\)之和
值得注意的是,由于单个点对于周长的贡献毫无意义,因此每个节点\(\{l, r\}\)都应维护区间\([l, r+1]\)的贡献。为了达成这个目的,在UPDATE时要将右端点\(-1\),在PUSHUP时再将其\(+1\)
同时,由于部分题目坐标为浮点数、坐标范围过大等原因,需要进行离散化(虽然本题不用)
最后,在查询时,只需要查询根节点的\(val\)就可以了

代码

#include <iostream>
#include <algorithm>
#include <cstring>
#include <unordered_map>

using namespace std;

const int N = 10005;

struct Line{
    int pos, a, b, tag;
    bool operator< (const Line W) const{
        if (pos != W.pos) return pos < W.pos;
        return tag > W.tag;
    }
}row[N], col[N]; //扫描线

struct Node{
    int val, cnt;
}treer[N * 4], treec[N * 4]; //线段树

int n;
int hai2vr[N], hai2vc[N]; //将下标转为值

int hav2i(int *hai2v, int n, int x){ //将值转换为下标
    return lower_bound(hai2v, hai2v + n, x) - hai2v;
}

int discr(Line *lines, int *hai2v){ //离散化
    sort(hai2v, hai2v + n);
    int i, j;
    for (i = 1, j = 1; i < n; i ++ )
        if (hai2v[i] != hai2v[i - 1]) hai2v[j ++ ] = hai2v[i];
    for (i = 0; i < n; i ++ ){
        Line &line = lines[i];
        line.a = hav2i(hai2v, j, line.a);
        line.b = hav2i(hai2v, j, line.b);
    }
    return j;
}

void pushup(Node *tree, int *hai2v, int u, int l, int r){
    if (tree[u].cnt) tree[u].val = hai2v[r + 1] - hai2v[l];
    else tree[u].val = tree[u << 1].val + tree[u << 1 | 1].val;
}

void update(Node *tree, int *hai2v, int u, int l, int r, int L, int R, int val){
    if (L <= l && r <= R){
        tree[u].cnt += val;
        pushup(tree, hai2v, u, l, r);
        return ;
    }
    int mid = l + r >> 1;
    if (L <= mid) update(tree, hai2v, u << 1, l, mid, L, R, val);
    if (R > mid) update(tree, hai2v, u << 1 | 1, mid + 1, r, L, R, val);
    pushup(tree, hai2v, u, l, r);
}

int solve(Line *lines, Node *tree, int *hai2v){ //解题主函数
    int nn = discr(lines, hai2v);
    sort(lines, lines + n);
    int ans = 0;
    for (int i = 0, last = 0; i < n; i ++ ){
        Line line = lines[i];
        update(tree, hai2v, 1, 0, nn - 1, line.a, line.b - 1, line.tag);
        ans += abs(tree[1].val - last);
        last = tree[1].val;
    }

    return ans;
}

int main(){
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ ){
        int x1, y1, x2, y2;
        scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
        row[i * 2] = {y1, x1, x2, 1};
        row[i * 2 + 1] = {y2, x1, x2, -1};
        col[i * 2] = {x1, y1, y2, 1};
        col[i * 2 + 1] = {x2, y1, y2, -1};
        hai2vr[i * 2] = x1, hai2vr[i * 2 + 1] = x2;
        hai2vc[i * 2] = y1, hai2vc[i * 2 + 1] = y2;
    }
    n *= 2;
    printf("%d\n", solve(row, treer, hai2vr) + solve(col, treec, hai2vc));
}

蒟蒻犯的若至错误

  • 线段树区间的叶子节点处理成了单点导致无效贡献
  • UPDATE操作中,被完全覆盖没有return
posted @ 2024-06-08 18:25  是一只小蒟蒻呀  阅读(16)  评论(0编辑  收藏  举报