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

122 lines
5.4 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;
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;
}