扫描线
\(\text{luogu-5490}\)
求 \(n\) 个四边平行于坐标轴的矩形的面积并。
\(1 \le n \le {10}^5\),\(0 \le x_1 < x_2 \le {10}^9\),\(0 \le y_1 < y_2 \le {10}^9\)。
扫描线可以用于快速计算多个矩形的面积并。扫描线的运行过程和名称类似,即一根线沿一个方向扫描所有图形,如下图。
可以发现,在扫描时我们将一个不那么规则的区域,切割成了互不相交的若干个矩形。这若干个矩形的面积之和就是所求的答案。
如何计算切割出的单个矩形面积呢?我们需要知道矩形的底和高。矩形的高很好求,即为排序后相邻两横线间的距离。那么如何求矩形的高呢?
想要维护矩形的高需要从扫描线的运行方式入手。每次扫到一根横线,我们或将这根横线加入,或将这根横线删除。
可以将一根横坐标在 \(l\sim r\) 范围内的的横线拆成 \(l\sim l+1,l+2\sim l+3,\dots,r-1\sim r\) 共 \(r-l\) 根长度为 \(1\) 的横线。容易想到,可以对所有长度为 \(1\) 的横线都开一个桶来储存这根横线的数量。储存值大于 \(0\) 的桶的数量即为当前矩形的高。
这样,将横线加入,就是把这根横线范围内所有桶的储存值 \(+1\),将横线删除,就是把这根横线范围内所有桶的储存值 \(-1\)。
无论是加入横线还是删除横线,每次的操作都是对一片区域内的桶 \(+1\) 或 \(-1\),最终查询的是储存值大于 \(0\) 的桶的数量。发现这种区间操作可以利用线段树来快速维护。
建树的过程和一般的线段树是一样的,这里不过多讲述。
线段树的每个节点,都维护一段区间内的桶,所以每个节点需要记录的信息有:
l,r:维护的区间,即左端点的右端点。w:这个区间被完整覆盖几次。x:这个区间内被覆盖过的桶的数量,即这个区间内被覆盖的长度。
所以在向下递归节点时,如果当前节点被操作区间完全覆盖,则直接对 cnt 进行修改;否则,如果操作区间和左儿子维护区间相交,则对左儿子递归,如果操作区间和右儿子维护区间相交,则对右儿子递归。
这些处理完之后,需要更新这个节点区间内被覆盖的长度 len。
void upd(ll p) {
if(t[p].w) t[p].x = h[t[p].r + 1] - h[t[p].l];
else if(t[p].l == t[p].r) t[p].x = 0;
else t[p].x = t[p << 1].x + t[p << 1 | 1].x;
return;
}
void update(ll p, ll l, ll r, ll x) {
if(l <= t[p].l && t[p].r <= r) { t[p].w += x, upd(p); return; }
if(l <= t[p << 1].r) update(p << 1, l, r, x);
if(t[p << 1 | 1].l <= r) update(p << 1 | 1, l, r, x);
upd(p); return;
}
和一般的线段树不同,我们不需要下传标记。因为加入一根横线,在之后必定会删除一根完全相同的横线。删除时会和加入时递归到同一个节点,并直接对这个节点修改。
由于根节点维护的是整个区间,所以查询根节点上储存的 len 即可知道当前被覆盖的桶的数量。
横坐标的范围较大,但可能出现的横坐标值的数量很少,我们可以对横坐标进行离散化处理。即每个桶记录的是一段区间,而不一定是一个长度为 \(1\) 的横线。
由于每个矩形会产生两个横坐标值,所以离散化后横坐标区间为 \(0\sim 2n\)。所以要开 \(2n\) 个桶,即线段树有 \(2n\) 个叶子节点。计算得出线段树需要不超过 \(4n\) 个节点,储存线段树的数组开到 \(4n\) 就够了,但需要注意在更新叶子节点时不要去查找它的子节点。
和一般的线段树类似,在递归时线段树的每一层最多只有 \(3\) 个节点被递归到,而线段树的层数约为 \(\log n\),故单次操作时间复杂度为 \(O(\log n)\)。共有 \(n\) 个矩形,每个矩形有 \(2n\) 个横线,故需要在线段树上操作 \(2n\) 次。忽略常数,总时间复杂度为 \(O(n\log n)\)。
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define ll long long
#define MAXN 200005
ll read() {
ll x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
struct tree { ll l, r, w, x; } t[MAXN << 2];
struct node { ll l, r, y, tg; };
vector<node> lin;
vector<ll> h;
ll n;
bool cmp(node pl, node pr) { return pl.y < pr.y; }
void build(ll p, ll l, ll r) {
t[p] = {l, r, 0, 0};
if(l == r) return;
ll mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
return;
}
void upd(ll p) {
if(t[p].w) t[p].x = h[t[p].r + 1] - h[t[p].l];
else if(t[p].l == t[p].r) t[p].x = 0;
else t[p].x = t[p << 1].x + t[p << 1 | 1].x;
return;
}
void update(ll p, ll l, ll r, ll x) {
if(l <= t[p].l && t[p].r <= r) { t[p].w += x, upd(p); return; }
if(l <= t[p << 1].r) update(p << 1, l, r, x);
if(t[p << 1 | 1].l <= r) update(p << 1 | 1, l, r, x);
upd(p); return;
}
int main() {
n = read();
for(int i = 1; i <= n; i ++) {
ll xl = read(), yl = read(), xr = read(), yr = read();
h.push_back(xl), h.push_back(xr);
lin.push_back({xl, xr, yl, 1});
lin.push_back({xl, xr, yr, -1});
}
sort(h.begin(), h.end());
sort(lin.begin(), lin.end(), cmp);
h.erase(unique(h.begin(), h.end()), h.end());
build(1, 0, h.size() - 2); ll res = 0;
for(int i = 0; i + 1 < lin.size(); i ++) {
ll pl = lower_bound(h.begin(), h.end(), lin[i].l) - h.begin();
ll pr = lower_bound(h.begin(), h.end(), lin[i].r) - h.begin();
update(1, pl, pr - 1, lin[i].tg);
res += t[1].x * (lin[i + 1].y - lin[i].y);
}
cout << res << "\n";
return 0;
}
\(\text{hdu-1542}\)
古希腊的几篇古籍里都提到了传说中的亚特兰蒂斯岛。有些书里甚至附带了岛上部分区域的地图。但遗憾的是,这些地图描绘的都是岛上不同的区域。你的朋友比尔想知道这些地图覆盖的总面积。你(不幸地)自告奋勇写个程序来算算这个总面积。
\(1 \le n \le 100\),\(0 \le x_1 < x_2 \le 10^5\),\(0 \le y_1 < y_2 \le 10^5\)。
和 luogu-5490 一摸一样,只不过有小数和多测。
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define ll long long
#define MAXN 200005
ll read() {
ll x = 0, f = 1;
char c = getchar();
while(c > 57 || c < 48) { if(c == 45) f = -1; c = getchar(); }
while(c >= 48 && c <= 57) { x = (x << 1) + (x << 3) + (c - 48); c = getchar(); }
return x * f;
}
struct tree { ll l, r, w; double x; } t[MAXN << 2];
struct node { double l, r, y; ll tg; };
vector<node> lin;
vector<double> h;
ll n, cnt;
bool cmp(node pl, node pr) { return pl.y < pr.y; }
void build(ll p, ll l, ll r) {
t[p] = {l, r, 0, 0};
if(l == r) return;
ll mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
return;
}
void upd(ll p) {
if(t[p].w) t[p].x = h[t[p].r + 1] - h[t[p].l];
else if(t[p].l == t[p].r) t[p].x = 0;
else t[p].x = t[p << 1].x + t[p << 1 | 1].x;
return;
}
void update(ll p, ll l, ll r, ll x) {
if(l <= t[p].l && t[p].r <= r) { t[p].w += x, upd(p); return; }
if(l <= t[p << 1].r) update(p << 1, l, r, x);
if(t[p << 1 | 1].l <= r) update(p << 1 | 1, l, r, x);
upd(p); return;
}
int main() {
while(cin >> n) {
if(!n) break; lin.clear(), h.clear();
for(int i = 1; i <= n; i ++) {
double xl, xr, yl, yr;
cin >> xl >> yl >> xr >> yr;
h.push_back(xl), h.push_back(xr);
lin.push_back({xl, xr, yl, 1});
lin.push_back({xl, xr, yr, -1});
}
sort(h.begin(), h.end());
sort(lin.begin(), lin.end(), cmp);
h.erase(unique(h.begin(), h.end()), h.end());
build(1, 0, h.size() - 2); double res = 0;
for(int i = 0; i + 1 < lin.size(); i ++) {
ll pl = lower_bound(h.begin(), h.end(), lin[i].l) - h.begin();
ll pr = lower_bound(h.begin(), h.end(), lin[i].r) - h.begin();
update(1, pl, pr - 1, lin[i].tg);
res += t[1].x * (lin[i + 1].y - lin[i].y);
}
printf("Test case #%lld\nTotal explored area: %.2lf\n\n", (++ cnt), res);
}
return 0;
}
本文来自博客园,作者:So_noSlack,转载请注明原文链接:https://www.cnblogs.com/So-noSlack/p/19721997

浙公网安备 33010602011771号