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