机试真题-扫描线算法-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):