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.

122 lines
5.4 KiB

2 years ago
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10; // 1e5个矩形
LL res; // 结果
int n; // n个矩形
int b[N << 2]; // 离散化数组,因为1e5个矩形2e5条边每条边两个y1,y2,所以需要4e5
// 从左到右的扫描线
struct ScanLine {
// y1,y2:这条线段上面的那个点和下面的那个点的y坐标
// x:在这里不会用到,只是用来排序而已
// k:是入边还是出边
int y1, y2, x, mark;
const bool operator<(const ScanLine &t) {
return x < t.x; // 以x坐标进行排序
}
} line[N << 1]; // 2e5条边
struct Node {
int l, r;
int len; // 统计值:有效长度和
int cnt; // 属性:入边:+1出边:-1,被多少个方格覆盖:cnt
} tr[N << 4]; // 开了16倍空间,矩形数量1e5,每个矩形2条竖边就是2e5,每条边两个y1,y2,就是4e5个点对于4e5需要开4倍空间就是16e5
void pushup(int u) {
// pushup利用左右儿子的信息更新u节点的统计信息
// 扫描线题目中统计信息就是有效区间长度即len
// cnt不是统计信息它可以视为某条线段树中某个区间的固有属性它不需要进行向上推送更新
// 整个区间被覆盖过有效len等于区间长度
// tr[u].l = L , tr[u].r + 1 = R => 坐标系中的L,R与线段树中[l,r]之间的映射关系
// 使用b[]离散数组通过索引号找出此位置上的原始值即y2,y1
if (tr[u].cnt)
tr[u].len = b[tr[u].r + 1] - b[tr[u].l];
else
tr[u].len = tr[u << 1].len + tr[u << 1 | 1].len;
// u节点所管辖的范围并非完整命中有效区间长度len=左节点len+右节点len
}
// 建树
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);
}
void modify(int u, int l, int r, int v) { // v:入边+1出边-1
if (l <= tr[u].l && r >= tr[u].r) { // 如果完整区间命中
tr[u].cnt += v; // 区间被覆盖次数+v
pushup(u); // 属性的更改,会造成统计信息的变更,所以需要pushup
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);
}
int main() {
#ifndef ONLINE_JUDGE
freopen("P5490.in", "r", stdin);
#endif
// 加快读入
ios::sync_with_stdio(false), cin.tie(0);
cin >> n;
int x1, x2, y1, y2;
for (int i = 1; i <= n; i++) {
cin >> x1 >> y1 >> x2 >> y2;
b[i * 2 - 1] = y1;
b[i * 2] = y2;
// 将y1,y2打入b数组使得1e9的范围离散化,扫描线从左向右
// 这里可以利用线段两个端点的特性一个是2*i-1,另一个是2*i
// 当然也可以使用游标的办法:++bl,效果是一样的只要是保证能塞进离散化数组b就行
// 将每个矩形的两条竖边记录扫描线数组
// 一个竖着的线段三个属性y1,y2,x
line[i * 2 - 1] = {y1, y2, x1, 1}; // 1:入边
line[i * 2] = {y1, y2, x2, -1}; // -1:出边
}
n *= 2; // 一个矩形两条线段,直接乘2方便后的计算,n的新含义扫描线条数
sort(line + 1, line + n + 1); // 按x坐标由小到大排序
sort(b + 1, b + n + 1); // 将y坐标离散化
int tot = unique(b + 1, b + n + 1) - b - 1; // 去重操作计算有多少个不同的y也就是这个图象有几个y
build(1, 1, tot - 1); // 建树
/*
Q:tot-1
[1,3][3,5]线3,[1,3),[3,5)
{1,2},{3,4},r-1
tot,线tot-1
*/
for (int i = 1; i < n; i++) { // 开始扫描面积并中扫描到n-1条线段即可,最后一条没用
int L = lower_bound(b + 1, b + tot + 1, line[i].y1) - b;
int R = lower_bound(b + 1, b + tot + 1, line[i].y2) - b;
// 在排好序并去重后的数组b中通过二分+原始值y1,查找到y1在离散化后数组b的位置位置编号就是y1在整体中的相当名次
// 上面构建build时也是用tot-1可丁可卯的大小创建的线段树所以这里也必须用find后的位置编号去指定位置上修改
modify(1, L, R - 1, line[i].mark);
// y1,y2:坐标系中的真实值
// L,R:离散化后的一一映射值
// 上面已经讨论过,坐标系中的线段区间 与 线段树中管控范围不是一一对应的,线段树区间是左闭右开的
// [L,R-1]-->(l,r)
res += (LL)tr[1].len * (line[i + 1].x - line[i].x);
// tr[1].len: 当前整体线段树中的可用长度和
// line[i + 1].x - line[i].x :下一条扫描线与本扫描线之间的宽度(扫描线从左向右x上的变化视为宽度)
// res+ : 长度和*宽度
}
// 输出res
printf("%lld\n", res);
return 0;
}