题目链接:https://www.acwing.com/problem/content/249/
解题思路:
这道题目的意思是想让我们去计算由许多矩形组成的多边形的总面积,并且给出的坐标可是浮点型的;对于这样有对称的线段出现并需要所框住区域中的某种属性的量,其实都是可以使用扫描线(但是还是得说应用的领域不大);但是在使用扫描线去计算其的面积的时候,需要去统计一个边的长度,这时候用线段树就会显得非常的巧,因为每对边都是成对出现的,所以省去了pushdown的操作,只需要一个pushup操作就可以完成了。
所以这道题目的思路是:首先对其坐标进行离散化,然后取出每个矩形的两天边放到vector容器中进行离散化;然后,将两条边分别赋予一个属性c来表示这条边是需要加进去还是拿出来。最后对其x进行排序,然后不断地去查询线段树中第一个节点的len即可计算面积。
但是在写代码的过程红也有一些需要注意的地方;首先,线段树中的每一个点其实存的是这个点所对应的那条边,即:i 表示 i 到 i + 1.所以有两个地方需要注意,一个是build的时候,一个是通过find去找l与r的时候,注意都需要多减去一个1.
贴上(蒟蒻)代码一份:
#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn = 200005;
int n;
struct Triangle {
double x, y1, y2;
int c;
bool operator< (const Triangle& t)const {
return x < t.x;
}
}triangle[maxn];
struct Treement {
int l, r;
int cnt;
double len;
}tr[4 * maxn];
vector<double> ve;
inline int find(double x) {
return lower_bound(ve.begin(), ve.end(), x) - ve.begin();
}
inline void pushup(int u) {
if(tr[u].cnt) tr[u].len = ve[tr[u].r + 1] - ve[tr[u].l];
else if(tr[u].l != tr[u].r) {
tr[u].len = tr[u << 1].len + tr[u << 1 | 1].len;
} else tr[u].len = 0;
}
inline void build(int u, int l, int r) {
tr[u] = {l, r, 0, 0};
if(l != r) {
int mid = l + r >> 1;
build(u << 1, l, mid); build(u << 1 | 1, mid + 1, r);
}
}
inline void modify(int u, int l, int r, int c) {
if(l <= tr[u].l && tr[u].r <= r) {
tr[u].cnt += c;
pushup(u);
return;
}
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) modify(u << 1, l, r, c);
if(r > mid) modify(u << 1 | 1, l, r, c);
pushup(u);
}
int main(void) {
// freopen("in.txt", "r", stdin);
int count = 0;
while(scanf("%d", &n) != EOF && n) {
ve.clear();
for(int i = 1, j = 0; i <= n; i ++) {
double x1, y1, x2, y2;
scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
triangle[j ++] = {x1, y1, y2, 1};
triangle[j ++] = {x2, y1, y2, -1};
ve.push_back(y1), ve.push_back(y2);
}
// puts("33");
sort(ve.begin(), ve.end());
ve.erase(unique(ve.begin(), ve.end()), ve.end());
build(1, 0, ve.size() - 2); //由于在这个线段树中,每个节点所代表的是一个线段,而不是一个点,
//所以这里需要在原来的基础上再 减去一个1;即:i:i - i + 1
sort(triangle, triangle + 2 * n);
double fi = 0;
double x1 = 0, x2 = 0;
for(int i = 0; i < 2 * n; i ++) {
//先计算面积
if(i > 0) fi += tr[1].len * (triangle[i].x - triangle[i - 1].x);
//再将这段线段放到线段树上去
int l, r;
l = find(triangle[i].y1);
r = find(triangle[i].y2) - 1;
modify(1, l, r, triangle[i].c);
}
printf("Test case #%d\nTotal explored area: %.2lf\n\n", ++ count, fi);
}
return 0;
}
总结:要注意细节,尤其是对线段树每个节点所代表的的含义要牢记。