You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

126 lines
5.7 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#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;
}