机试真题-扫描线算法-KS 24.05.08.02

机试真题-扫描线算法-KS 24.05.08.02

1. 题目描述

游戏的UI渲染中,表现为若干矩形区域的多层极覆盖绘制操作。每个UI渲染区域都是一个矩形,并且都是和屏幕边框平行或垂直的。每个渲染的矩形区域也可以部分或全部被其他区域覆盖。所有矩形并集的边界的长度称为周长。如下示意图:
在这里插入图片描述

2. 输入输出描述

输入

第一行输入数组的长度n(n范围在0到5000)接下来每行输入矩形的左下和右上的坐标点x1,y1,x2,y2

输出

当前图形的周长

示例

输入:

20
0 0 20 20
5 5 15 15
8 8 12 12
-5 -5 25 25
30 0 40 20
35 5 45 15
38 8 42 12
25 -5 35 25
0 25 20 35
5 30 15 40
8 33 12 37
-5 20 25 40
30 25 40 35
35 30 45 40
38 33 42 37
25 20 35 40
15 15 25 25
18 18 22 22
10 10 30 30
-10 -10 50 50

输出:

240

3. 解题思路

可以通过计算每个矩形边的贡献值来求解总周长。这里的关键在于识别哪些边是未被其他矩形覆盖的,因为只有未被覆盖的边才能对总周长做出贡献。一个有效的方法是使用扫描线算法及边界计数法来求解。
相关代码:

#include <iostream>
#include <vector>
#include <map>
#include <algorithm>
using namespace std;

struct Event {
    int x, y1, y2;
    int type; // 1为矩形进入,-1为矩形离开
    Event(int x, int y1, int y2, int type) : x(x), y1(y1), y2(y2), type(type) {}
    bool operator<(const Event& other) const {
        // 如果x相同,则type小的在前面,保证同一个x位置上先添加再删除
        return x < other.x || (x == other.x && type < other.type);
    }
};

int calculatePerimeter(vector<vector<int>>& rectangles) {
    vector<Event> events;
    for (const auto& rect : rectangles) {
        int x1 = rect[0], y1 = rect[1], x2 = rect[2], y2 = rect[3];
        events.emplace_back(x1, y1, y2, 1); // 进入事件
        events.emplace_back(x2, y1, y2, -1); // 离开事件
    }

    sort(events.begin(), events.end());

    map<int, int> activeEdges; // 活动边,存储y1到y2之间的边数量
    int prevX = events[0].x;
    int perimeter = 0;
    for (const auto& event : events) {
        int curX = event.x;
        // 计算上一个X到当前X之间的边长贡献
        if (!activeEdges.empty()) {
            int length = 0;
            int prevY = -1;
            for (const auto& edge : activeEdges) {
                if (prevY != -1) { // 排除初始情况
                    length += max(0, edge.first - prevY);
                }
                prevY = edge.second;
            }
            perimeter += length * (curX - prevX);
        }
        // 更新活动边
        if (event.type == 1) { // 进入,添加边
            activeEdges[event.y1] += 1;
            activeEdges[event.y2] -= 1;
        } else { // 离开,删除边
            activeEdges[event.y1] -= 1;
            if (activeEdges[event.y1] == 0) {
                activeEdges.erase(event.y1);
            }
            activeEdges[event.y2] += 1;
            if (activeEdges[event.y2] == 0) {
                activeEdges.erase(event.y2);
            }
        }
        prevX = curX;
    }

    return perimeter;
}

int main() {
    int n;
    cin >> n;
    vector<vector<int>> rectangles(n, vector<int>(4));
    for (int i = 0; i < n; ++i) {
        cin >> rectangles[i][0] >> rectangles[i][1] >> rectangles[i][2] >> rectangles[i][3];
    }

    int totalPerimeter = calculatePerimeter(rectangles);
    cout << totalPerimeter << endl;
    return 0;
}

扫描线算法的相关介绍与使用
https://zhuanlan.zhihu.com/p/103616664
扫描线算法相关介绍与使用方法(chatgpt):
在这里插入图片描述

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值