扫描线
Abstract
介绍一下扫描线的经典用法。
命名空间还挺好用的。
A-扫描线(模板)
Idea
想象现在有一根线正在从左向右扫描,那么,我们就可以通过纵坐标上区间的覆盖情况去确定扫过的矩形覆盖的面积,区间覆盖情况可以用线段树去维护。实现细节见代码注释。
Code
#include <bits/stdc++.h>
#define int long long
int n;
namespace seg
{
const int maxn = 1001000;
int index[maxn * 4];
// Mat 结构体用于描述矩形的左右两条边
struct Mat
{
// x 表示此边的横坐标
// y1 y2 表示此边上下端点的纵坐标
// flag 为 -1 表示这是靠右的边,1 表示这是靠左的边
int x, y1, y2, flag;
} mat[maxn * 4];
bool cmp(Mat a, Mat b)
{
return a.x < b.x;
}
// Node 节点描述的是区间的覆盖情况
struct Node
{
// l r 记录区间的左右端点
// sum 记录区间内被覆盖的长度
int l, r, sum;
} node[maxn * 4];
// 懒惰标记,记录这个区间被覆盖的次数
int lazy[maxn * 4];
// 为了缩减代码量,添加的左右儿子以及中间点的宏定义
#define l(x) (x << 1)
#define r(x) (x << 1 | 1)
#define m(l, r) (l + r >> 1)
// 用子节点更新父节点
void pushUp(int rt)
{
// 如果懒惰标记存在,那么这个区间肯定全被覆盖了
if (lazy[rt] > 0)
{
node[rt].sum = node[rt].r - node[rt].l;
}
else // 否则,用子节点来更新父节点
{
node[rt].sum = node[l(rt)].sum + node[r(rt)].sum;
}
return;
}
// 建树
void build(int rt, int l, int r)
{
// 由于我们考察的对象是区间,所以这里不是 l == r
if (r - l > 1)
{
node[rt].l = index[l];
node[rt].r = index[r];
build(l(rt), l, m(l, r));
build(r(rt), m(l, r), r);
pushUp(rt);
}
else
{
node[rt].l = index[l];
node[rt].r = index[r];
node[rt].sum = 0;
}
return;
}
void update(int rt, int l, int r, int flag)
{
if (node[rt].l == l && node[rt].r == r)
{
lazy[rt] += flag;
pushUp(rt);
return;
}
else
{
if (node[l(rt)].r > l)
{
update(l(rt), l, std::min(r, node[l(rt)].r), flag);
}
if (node[r(rt)].l < r)
{
update(r(rt), std::max(l, node[r(rt)].l), r, flag);
}
pushUp(rt);
}
}
};
signed main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cin >> n;
for (int i = 1; i <= n; i++)
{
int x1, y1, x2, y2;
std::cin >> x1 >> y1 >> x2 >> y2;
// 我们总是规定 x1 >= x2,方便确定左右边
if (x1 > x2)
{
std::swap(x1, x2);
}
// 规定 y1 <= y2 ,方便更新线段树
if (y1 > y2)
{
std::swap(y1, y2);
}
// 记录矩形
seg::mat[i].x = x1;
seg::mat[i].y1 = y1;
seg::mat[i].y2 = y2;
seg::mat[i].flag = 1;
seg::mat[i + n].x = x2;
seg::mat[i + n].y1 = y1;
seg::mat[i + n].y2 = y2;
seg::mat[i + n].flag = -1;
// 离散化
seg::index[i] = y1, seg::index[i + n] = y2;
}
// 初始化
std::sort(seg::index + 1, seg::index + 1 + 2 * n);
std::sort(seg::mat + 1, seg::mat + 1 + 2 * n, seg::cmp);
seg::build(1, 1, 2 * n);
memset(seg::lazy, 0, sizeof seg::lazy);
int ans = 0;
// 先把第一条边单独拿出来更新
seg::update(1, seg::mat[1].y1, seg::mat[1].y2, seg::mat[1].flag);
// 按顺序遍历矩形每一条边
for (int i = 2; i <= 2 * n; i++)
{
// 更新答案
ans += (seg::mat[i].x - seg::mat[i - 1].x) * seg::node[1].sum;
// 更新区间覆盖情况
seg::update(1, seg::mat[i].y1, seg::mat[i].y2, seg::mat[i].flag);
}
std::cout << ans;
return 0;
}
B-奇偶区间(模板进阶)
Idea
这题和上题稍有不同,写了这题,也能加深自己对上一题的认识。
这题的灵魂就在于懒惰标记,注意,这个懒惰标记是没有下传的!我们在更新这个区间的覆盖情况时,如果这个区间的懒惰标记非 0 ,那么这说明它一定正被某一个矩形完全覆盖,只有这个矩形的右边也被扫过,这个懒惰标记才有可能归零!在有懒惰标记的情况下,这个区间自然是被完全覆盖的,那么,如何确定奇偶区间覆盖情况呢?我们可以用子节点的覆盖情况来确定,具体的实现细节见代码注释。
Code
#include <bits/stdc++.h>
#define int long long
int n;
namespace seg
{
const int maxn = 1001000;
int index[maxn * 4];
struct Mat
{
int x, y1, y2, flag;
} mat[maxn * 4];
bool cmp(Mat a, Mat b)
{
return a.x < b.x;
}
struct Node
{
// sum1 sum2 分别表示被奇/偶数个矩形覆盖的长度
// 显然的,sum1 + sum2 == sum
int l, r, sum, sum1, sum2;
} node[maxn * 4];
int lazy[maxn * 4];
#define l(x) (x << 1)
#define r(x) (x << 1 | 1)
#define m(l, r) (l + r >> 1)
void pushUp(int rt)
{
if (lazy[rt] > 0)
{
node[rt].sum = node[rt].r - node[rt].l;
if (lazy[rt] & 1)
{
// 如果懒惰标记是奇数,那么子区间的奇数覆盖数之和就是父区间的偶数覆盖数
node[rt].sum2 = node[l(rt)].sum1 + node[r(rt)].sum1;
node[rt].sum1 = node[rt].sum - node[rt].sum2;
}
else
{
// 同理的
node[rt].sum1 = node[l(rt)].sum1 + node[r(rt)].sum1;
node[rt].sum2 = node[rt].sum - node[rt].sum1;
}
}
else
{
node[rt].sum = node[l(rt)].sum + node[r(rt)].sum;
node[rt].sum1 = node[l(rt)].sum1 + node[r(rt)].sum1;
node[rt].sum2 = node[l(rt)].sum2 + node[r(rt)].sum2;
}
return;
}
void build(int rt, int l, int r)
{
if (r - l > 1)
{
node[rt].l = index[l];
node[rt].r = index[r];
build(l(rt), l, m(l, r));
build(r(rt), m(l, r), r);
pushUp(rt);
}
else
{
node[rt].l = index[l];
node[rt].r = index[r];
node[rt].sum = 0;
}
return;
}
void update(int rt, int l, int r, int flag)
{
if (node[rt].l == l && node[rt].r == r)
{
lazy[rt] += flag;
pushUp(rt);
return;
}
else
{
if (node[l(rt)].r > l)
{
update(l(rt), l, std::min(r, node[l(rt)].r), flag);
}
if (node[r(rt)].l < r)
{
update(r(rt), std::max(l, node[r(rt)].l), r, flag);
}
pushUp(rt);
}
}
};
signed main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cin >> n;
for (int i = 1; i <= n; i++)
{
int x1, y1, x2, y2;
std::cin >> x1 >> y1 >> x2 >> y2;
if (y1 > y2)
{
std::swap(y1, y2);
std::swap(x1, x2);
}
seg::mat[i].x = x1;
seg::mat[i].y1 = y1;
seg::mat[i].y2 = y2;
seg::mat[i].flag = 1;
seg::mat[i + n].x = x2;
seg::mat[i + n].y1 = y1;
seg::mat[i + n].y2 = y2;
seg::mat[i + n].flag = -1;
seg::index[i] = y1, seg::index[i + n] = y2;
}
std::sort(seg::index + 1, seg::index + 1 + 2 * n);
std::sort(seg::mat + 1, seg::mat + 1 + 2 * n, seg::cmp);
seg::build(1, 1, 2 * n);
memset(seg::lazy, 0, sizeof seg::lazy);
// ans1 表示奇数覆盖区间的面积
int ans = 0, ans1 = 0;
seg::update(1, seg::mat[1].y1, seg::mat[1].y2, seg::mat[1].flag);
for (int i = 2; i <= 2 * n; i++)
{
ans += (seg::mat[i].x - seg::mat[i - 1].x) * seg::node[1].sum;
ans1 += (seg::mat[i].x - seg::mat[i - 1].x) * seg::node[1].sum1;
seg::update(1, seg::mat[i].y1, seg::mat[i].y2, seg::mat[i].flag);
}
std::cout << ans1 << std::endl;
std::cout << ans - ans1;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!