Files
python/TangDou/AcWing/SegmentTree/HDU1828_Shu.cpp
HuangHai 1f397eca87 'commit'
2025-08-30 18:35:01 +08:00

126 lines
5.7 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <bits/stdc++.h>
using namespace std;
const int N = 10010;
typedef long long LL;
struct ScanLine {
int y1, y2, x, mark;
const bool operator<(const ScanLine &t) const {
// 按x由小到大排序,对应扫描线从左到右。如果x坐标一样的两条竖线,入边=1在前,出边=-1在后
// 如果出现了两条x坐标相同的扫描线,也就是两矩形相邻
// 那么需要先扫入边再扫出边,否则就会多算这条边
// 这个对面积并无影响但对周长并有影响
// hack 数据:2 0 0 4 4 4 0 8 4 输出应为:24
// 加上这句判断,答案正确:24
// 去掉这句判断,答案错误:32,也就是把中间那两条重合的边都加上了边长,加了两遍。
// 解决办法:先算入边再算出边即可
if (x == t.x) return mark > t.mark;
return x < t.x;
}
} line[N];
// 线段树结构体
struct Node {
int l, r; // 边界
int cnt; // 被整体覆盖了几次
int len; // 被覆盖的长度
int c; // 区间中总共有多少裸露的断点
bool lc, rc; // 左端点是否被覆盖,右端点是否被覆盖
} tr[N << 2];
int b[N]; // 离散化数组
void pushup(int u) {
if (tr[u].cnt) { // 如果u被完整覆盖
tr[u].len = b[tr[u].r + 1] - b[tr[u].l]; // 利用离散化数组,计算在线段树中节点,映射回原始数据的R,L,计算两者之间的差值,就是y轴上的坐标差,也就是矩形的竖边边长
// 因为u管辖的范围被整体覆盖,所以,被覆盖的长度需要换算成真实的长度
tr[u].c = 2; // 区间中裸露的断点有2个
tr[u].lc = tr[u].rc = true; // 左端点被覆盖,右端点被覆盖
} else { // 如果没有被完整覆盖
tr[u].len = tr[u << 1].len + tr[u << 1 | 1].len; // 被覆盖的长度=左儿子被覆盖的长度+右儿子被覆盖的长度
tr[u].c = tr[u << 1].c + tr[u << 1 | 1].c; // 区间中总共有多少条竖线=左儿子有多少条竖线+右儿子有多少条竖线
tr[u].lc = tr[u << 1].lc; // 如果左儿子左端点被覆盖,那么自己的左端点也肯定被覆盖
tr[u].rc = tr[u << 1 | 1].rc; // 如果右儿子右端点被覆盖,那么自己的右端点也肯定被覆盖
if (tr[u << 1 | 1].lc && tr[u << 1].rc) tr[u].c -= 2; // 如果做儿子右端点和右儿子左端点都被覆盖,那么中间就是连续的一段,所以要 -= 1
}
}
// 区间修改,没有使用懒标记
void modify(int u, int l, int r, int v) {
if (l <= tr[u].l && r >= tr[u].r) {
tr[u].cnt += v;
pushup(u);
return;
}
int mid = (tr[u].l + tr[u].r) >> 1;
if (l <= mid) modify(u << 1, l, r, v);
if (r > mid) modify(u << 1 | 1, l, r, v);
pushup(u);
}
// 构建线段树
void build(int u, int l, int r) {
tr[u].l = l, tr[u].r = r;
if (l == r) return;
int mid = (l + r) >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
}
int main() {
#ifndef ONLINE_JUDGE
freopen("HDU1828.in", "r", stdin);
#endif
ios::sync_with_stdio(false), cin.tie(0);
int n;
while (cin >> n) {
memset(tr, 0, sizeof tr); // 多组测试数据,注意清空数组
int x1, y1, x2, y2;
for (int i = 1; i <= n; i++) {
cin >> x1 >> y1 >> x2 >> y2;
line[i * 2 - 1] = {y1, y2, x1, 1}; // 入边
line[i * 2] = {y1, y2, x2, -1}; // 出边
b[i * 2 - 1] = y1, b[i * 2] = y2; // 将y坐标记录到离散化数组中
}
// n个矩形,2*n条竖边
n <<= 1;
sort(line + 1, line + n + 1); // 将扫描线按x由小到大排序,竖着的扫描线
sort(b + 1, b + n + 1); // 将离散化数组排序,去重
int tot = unique(b + 1, b + 1 + n) - b - 1;
build(1, 1, tot - 1); // 构建线段树
LL res = 0; // 结果值
int last = 0; // 前一条扫描线中有效的竖线长度和
for (int i = 1; i < n; i++) { // 前n-1条竖边,最后一条单独处理
int L = lower_bound(b + 1, b + tot + 1, line[i].y1) - b; // 通过二分查找出原始值y1在离散化数组中的位置
int R = lower_bound(b + 1, b + tot + 1, line[i].y2) - b;
modify(1, L, R - 1, line[i].mark); // 对于映射后的区间[L,R)=[L,R-1]进行修改,++--
// 横线和
// tr[1].c: 横边的有效个数,一般情况下,一条竖边是两个点,如果有粘在一起的,就需要特殊计算,比如-2
// line[i + 1].x - line[i].x:竖着的扫描线,横坐标的差,就是个矩形的宽度
res += (LL)tr[1].c * (line[i + 1].x - line[i].x);
// 竖线和
// 每次的边长和,与上次的边长和差的绝对值,为竖着的有效长度
res += (LL)abs(tr[1].len - last);
// 记录下来本次的竖着的有效长度,以便后续使用
last = tr[1].len;
}
// 单独处理最后一条边,对于前面每一条,都是line[i+1].x-line[i].x,而最后一条没有,但它确实是存在长度,需要单独计算
res += line[n].y2 - line[n].y1;
// 输出结果
printf("%lld\n", res);
}
return 0;
}