扫描线算法
扫描线算法:
扫描线算法可以解决多个矩形重合求卖面积或周长
如何求彩色部分的面积呢
1 > 暴力 :
模拟 n*n 的表格遍历每个格子,数据一多肯定是吃不消的
2 > 扫描线
用一根假想的线扫过整个平面,遇到图形的边界就更新当前覆盖的区域,同时累加面积。
大体步骤: 1>把每个矩形拆除左边界( +1 ),右边界( -1)。
2>对x进行排序
3>扫描
- 从左到右移动扫描线,遇到“左边界”就把对应的区间标记为被覆盖,遇到“右边界”就取消覆盖。
- 每次移动扫描线时,计算当前被覆盖的总长度 × 移动的距离,累加到总面积中。
3 > 用线段树快速计算当前被覆盖的总高度。就类似与用线段树求解线段和
代码实现
线段树的实现
struct segNode { // 线段树节点的类型
int l, r; // 垂直与 x 轴的左边界和右边界
int len; // 线段的长度
int cnt; // 线端被完全覆盖的数目
};
class SegmentTree {
private :
void build(int u,int l,int r); // 初始化线段树
void push_up(int u) ; // 向上更新节点
public :
vector<segNode> nodes; // 线段节点
SegmentTree(int N) { // 构造函数 参数 N 是整个线段的(y轴)长度不是线段的个数
nodes.resize(N << 2); // 线段树需要 4 倍的空间
build(1,0,N); // 以 nodes[1] 为头节点
}
void modify(int u,int l,int r,int d); // 更新区间
};
build
void SegmentTree::build(int u,int l,int r)
{
nodes[u] = {l,r,0,0}; // 初始化节点数组
if (l == r) {
return;
}
int mid = (l + r) >> 1; // 中点
build(u << 1, l, mid); // 递归更新左子树
build(u << 1 | 1, mid + 1, r); // 更新右子树
}
modify
void SegmentTree::modify(int u, int l, int r, int d)
{
if (l <= nodes[u].l && nodes[u].r <= r) { // 区间完全包含在 (l,r) 中
nodes[u].cnt += d; // d 是左边界和右边界的标志 左(1)右(-1)
push_up(u); // 向上更新
}
else { // 可能不包含也可能不完全包含
int mid = (nodes[u].l + nodes[u].r) >> 1;
if (mid >= l) {
modify(u << 1, l, r, d); // 更新左左子树
}
if (mid < r ) {
modify(u << 1 | 1, l, r, d); // 更新右子树
}
push_up(u);
}
}
push_up
void SegmentTree::push_up(int u)
{
if (nodes[u].cnt > 0) { // 被完全覆盖
nodes[u].len = nodes[u].r - nodes[u].l + 1; // 这个区间的长度
}
else if(nodes[u].l == nodes[u].r) { // 叶子节点不能 && cnt == 0
nodes[u].len = 0;
}
else { // 非叶子节点可能没有被完全覆盖
nodes[u].len = nodes[u << 1].len + nodes[u << 1 | 1].len; // 加上左右子树的长度
}
}
整个算法的实现
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
struct segNode{ // 线段树节点的类型
int l, r; // 垂直与 x 轴的左边界和右边界
int len; // 线段的长度
int cnt; // 线端被完全覆盖的数目
};
class SegmentTree {
private :
void build(int u,int l,int r); // 初始化线段树
void push_up(int u) ; // 向上更新节点
public :
vector<segNode> nodes; // 线段节点
SegmentTree(int N) {
nodes.resize(N << 2); // 线段树需要 4 倍的空间
build(1,0,N); // 以 nodes[1] 为头节点
}
void modify(int u,int l,int r,int d); // 更新区间
};
void SegmentTree::build(int u,int l,int r)
{
nodes[u] = {l,r,0,0}; // 初始化节点数组
if (l == r) {
return;
}
int mid = (l + r) >> 1; // 中点
build(u << 1, l, mid); // 递归更新左子树
build(u << 1 | 1, mid + 1, r); // 更新右子树
}
void SegmentTree::push_up(int u)
{
if (nodes[u].cnt > 0) { // 被完全覆盖
nodes[u].len = nodes[u].r - nodes[u].l + 1; // 这个区间的长度
}
else if(nodes[u].l == nodes[u].r) { // 叶子节点不能 && cnt == 0
nodes[u].len = 0;
}
else { // 非叶子节点可能没有被完全覆盖
nodes[u].len = nodes[u << 1].len + nodes[u << 1 | 1].len; // 加上左右子树的长度
}
}
void SegmentTree::modify(int u, int l, int r, int d)
{
if (l <= nodes[u].l && nodes[u].r <= r) { // 区间完全包含在 (l,r) 中
nodes[u].cnt += d; // d 是左边界和右边界的标志 左(1)右(-1)
push_up(u); // 向上更新
}
else { // 可能不包含也可能不完全包含
int mid = (nodes[u].l + nodes[u].r) >> 1;
if (mid >= l) {
modify(u << 1, l, r, d); // 更新左左子树
}
if (mid < r ) {
modify(u << 1 | 1, l, r, d); // 更新右子树
}
push_up(u);
}
}
constexpr int N = 1e4 + 5;
struct node { // 用来储存线段
int x, y1, y2;
int w; // 是左边界和右边界的标志 左(1)右(-1)
};
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
vector<node>arr;
arr.reserve(2 * n); // 线段是正方形的 2 倍
for (int i = 0; i < n; ++i) {
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
arr.push_back({x1,y1,y2-1,1}); // 左边界 在像素格中线段应该是 [y1,y2) 左闭右开
arr.push_back({x2,y1,y2-1,-1}); // 右边界
}
sort(arr.begin(), arr.end(), [](auto &a,auto &b) {
return a.x < b.x; // 按x的升序排列
});
SegmentTree seg(N); // 初始化线段树
seg.modify(1,arr[0].y1,arr[0].y2,arr[0].w); // 初始化第一条边
long long res = 0;
for (int i = 1; i < 2 * n; ++i) { // 枚举每一条边
res += seg.nodes[1].len * (arr[i].x - arr[i-1].x);
seg.modify(1,arr[i].y1,arr[i].y2 ,arr[i].w);
}
cout << res << endl;
return 0;
}
如果线段长度为 1e9 会爆空间,可以用离散化优化
离散化优化
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
struct segNode { // 线段树节点的类型
int l, r; // 垂直与 x 轴的左边界和右边界
int len; // 线段的长度
int cnt; // 线端被完全覆盖的数目
};
class SegmentTree {
private:
void build(int u, int l, int r); // 初始化线段树
void push_up(int u); // 向上更新节点
public:
vector<segNode> nodes; // 线段节点
vector<int> ys; // 离散化数组
SegmentTree(int N) {
nodes.resize(N << 2); // 线段树需要 4 倍的空间
build(1, 0, N); // 以 nodes[1] 为头节点
}
void modify(int u, int l, int r, int d); // 更新区间
};
void SegmentTree::build(int u, int l, int r)
{
nodes[u] = { l,r,0,0 }; // 初始化节点数组
if (l == r) {
return;
}
int mid = (l + r) >> 1; // 中点
build(u << 1, l, mid); // 递归更新左子树
build(u << 1 | 1, mid + 1, r); // 更新右子树
}
void SegmentTree::push_up(int u)
{
if (nodes[u].cnt > 0) { // 被完全覆盖
nodes[u].len = ys[nodes[u].r + 1] - ys[nodes[u].l]; // 离散化后的索引区间[l, r]对应实际坐标中的区间[ys[l], ys[r+1]),其长度为ys[r+1] - ys[l]
}
else if (nodes[u].l == nodes[u].r) { // 叶子节点不能 && cnt == 0
nodes[u].len = 0;
}
else { // 非叶子节点可能没有被完全覆盖
nodes[u].len = nodes[u << 1].len + nodes[u << 1 | 1].len; // 加上左右子树的长度
}
}
void SegmentTree::modify(int u, int l, int r, int d)
{
if (l <= nodes[u].l && nodes[u].r <= r) { // 区间完全包含在 (l,r) 中
nodes[u].cnt += d; // d 是左边界和右边界的标志 左(1)右(-1)
push_up(u); // 向上更新
}
else { // 可能不包含也可能不完全包含
int mid = (nodes[u].l + nodes[u].r) >> 1;
if (mid >= l) {
modify(u << 1, l, r, d); // 更新左左子树
}
if (mid < r) {
modify(u << 1 | 1, l, r, d); // 更新右子树
}
push_up(u);
}
}
constexpr int N = 1e4 + 5;
struct node { // 用来储存线段
int x, y1, y2;
int w; // 是左边界和右边界的标志 左(1)右(-1)
};
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
// 离散化存 y
vector<int>ys;
vector<node>arr;
arr.reserve(n << 1); // 线段是正方形的 2 倍
for (int i = 0; i < n; ++i) {
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
ys.emplace_back(y1);
ys.emplace_back(y2);
arr.push_back({ x1,y1,y2,1 }); // 左边界 在像素格中线段应该是 [y1,y2) 左闭右开
arr.push_back({ x2,y1,y2,-1 }); // 右边界
}
sort(ys.begin(), ys.end());
ys.erase(unique(ys.begin(), ys.end()), ys.end()); // 去重
// 建立映射
auto find = [&](int y) {
return lower_bound(ys.begin(), ys.end(), y) - ys.begin();
};
// arr
for (auto& no : arr) {
no.y1 = find(no.y1);
no.y2 = find(no.y2);
}
sort(arr.begin(), arr.end(), [](auto& a, auto& b) {
return a.x < b.x; // 按x的升序排列
});
SegmentTree seg(ys.size() - 1); // 有 ys.size() 个点一共有 ys.size() - 1 条边
seg.ys = ys;
seg.modify(1, arr[0].y1, arr[0].y2 - 1, arr[0].w); // 初始化第一条边
long long res = 0;
for (int i = 1; i < 2 * n; ++i) { // 枚举每一条边
res += seg.nodes[1].len * (arr[i].x - arr[i - 1].x);
seg.modify(1, arr[i].y1, arr[i].y2 - 1, arr[i].w); // 线段树维护的是完全覆盖的区间 [y1,y2)
}
cout << res << endl;
return 0;
}