扫描线算法

扫描线算法

14.油漆面积 - 蓝桥云课

扫描线算法:

扫描线算法可以解决多个矩形重合求卖面积或周长

在这里插入图片描述

如何求彩色部分的面积呢

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值