一、内容
给定平面上若干矩形,求出被这些矩形覆盖过至少两次的区域的面积.
Input
输入数据的第一行是一个正整数T(1<=T<=100),代表测试数据的数量.每个测试数据的第一行是一个正整数N(1<=N<=1000),代表矩形的数量,然后是N行数据,每一行包含四个浮点数,代表平面上的一个矩形的左上角坐标和右下角坐标,矩形的上下边和X轴平行,左右边和Y轴平行.坐标的范围从0到100000.
注意:本题的输入数据较多,推荐使用scanf读入数据.
Output
对于每组测试数据,请计算出被这些矩形覆盖过至少两次的区域的面积.结果保留两位小数.
Sample Input
2
5
1 1 4 2
1 3 3 7
2 1.5 5 4.5
3.5 1.25 7.5 4
6 3 10 7
3
0 0 1 1
1 0 2 1
2 0 3 1
Sample Output
7.63
0.00
二、思路
- 覆盖一次的扫描线模板
- 第一种思路:我们只需要每次求出包含2次以上的长度即可,由于我们扫描线在update()和查询的时候都是没有进行pushdown()操作的,在求包含一次以上的长度时不会出现错误,但是当2次以上的时候有些区间便会出错(因为父亲的标记没有下方到这个子区间)。故我们只需改变原来这个优化,在查询和修改的时候恢复它的标记下放。在pushup的时候修改下判断,判断2次以上的长度。最后查询的len就是最终包含2次以上的长度了。
- 第二种思路:还是按照优化的策略, 只是对线段树新增了一个属性len2(代表包含2次以上的长度)。
1.当cnt >= 2的时候,代表的收这个区间是被完整覆盖了2次以上,那么len2直接等于这个区间的长度即可。
2.当cnt==1的时候,代表这个区间被完整覆盖了1次,那么若子区间是被覆盖了一次,那么必定加上父区间的一次,肯定是2次以上。 所以len2 = 左区间的len1 + 右区间的len1. (len1是包含了1次以上的长度)
3.当cnt == 0的时候,代表这个区间没有被完全包含,那么这时候的len2可以从2个子区间的len2得来。
4.根节点,若根节点的cnt<=1,那么直接等于0,因为它无子区间。 - 最后说的是题目样例有误差。好像是四舍五入了。
三、代码
第一种思路:
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 2005;
struct Line {
int st; //代表1 -1 2种状态
double s, e, x;
bool operator < (const Line & w) const {
return x < w.x; //按照x小的排在前面
}
} line[N];
struct Node {
int cnt, lazy; //代表被包含的次数
double len; //这个区间被包含的长度
} tr[N << 2];
int n, cnt, t; // cnt代表离散化后数组里面元素的个数
double fy[N], x1, x2, y1, y2;
void build(int id, int l, int r) {
tr[id].cnt = tr[id].lazy = tr[id].len = 0;
if (l == r) return;
int mid = (l + r) >> 1;
build(id << 1, l, mid);
build(id << 1 | 1, mid + 1, r);
}
void pushup(int id, int l, int r) {
if (tr[id].cnt >= 2) { // 代表id这个区间被覆盖了
tr[id].len = fy[r + 1] - fy[l]; //因为一个点代表的是一个区间 那r这个点的区间 就是[fy[r], fy[r + 1]]这段长度
} else if (l != r) { //如果这整个区间没有被包含 那么就由儿子区间组成
tr[id].len = tr[id << 1].len + tr[id << 1 | 1].len;
} else tr[id].len = 0; //叶子结点
}
void pushdown(int id, int l, int r) {
if (tr[id].lazy == 0) return;
tr[id << 1].cnt += tr[id].lazy;
tr[id << 1 | 1].cnt += tr[id].lazy;
tr[id << 1].lazy += tr[id].lazy;
tr[id << 1 | 1].lazy += tr[id].lazy;
tr[id].lazy = 0;
}
void query(int id, int l, int r) {
if (l == r) {
pushup(id, l, r);
return;
}
pushdown(id, l, r); //查询的时候将所有层次的标记进行下放
int mid = (l + r) >> 1;
query(id << 1, l, mid);
query(id << 1 | 1, mid + 1, r);
pushup(id, l, r);
}
void update(int id, int l, int r, int x, int y, int d) {
if (x <= l && r <= y) {
tr[id].cnt += d;
tr[id].lazy += d;
pushup(id, l, r); //及时更新这段区间 因为父亲区间可能需要用到我这个区间的len
return;
}
int mid = (l + r) >> 1;
pushdown(id, l, r);
if (x <= mid) update(id << 1, l, mid, x, y, d);
if (y > mid) update(id << 1 | 1, mid + 1, r, x, y, d);
pushup(id, l, r);
}
int find(double y) {
return lower_bound(fy + 1, fy + 1 + cnt, y) - fy;
}
int main() {
scanf("%d", &t);
while (t--) {
scanf("%d", &n);
for (int i = 1, j = 1; i <= n; i++) {
scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
line[j].s = y1, line[j].e = y2, line[j].x = x1, line[j].st = 1, fy[j++] = y1;
line[j].s = y1, line[j].e = y2, line[j].x = x2, line[j].st = -1, fy[j++] = y2;
}
sort(line + 1, line + 1 + 2 * n);
sort(fy + 1, fy + 1 + 2 * n);
cnt = unique(fy + 1, fy + 1 + 2 * n) - fy - 1; //获取去重后元素的个数
build(1, 1, cnt - 1); //一个点代表的是一个区间, 一共有cnt个点 所以只有cnt-1个区间
double ans = 0;
for (int i = 1; i <= 2 * n; i++) {
query(1, 1, cnt - 1); //先查询一次才能够使用1这个节点的区间
ans += tr[1].len * (line[i].x - line[i - 1].x);
update(1, 1, cnt - 1, find(line[i].s), find(line[i].e) - 1, line[i].st); //这里-1是因为每个点代表的是一个区间 我们不能包含e那个点所代表的区间
}
printf("%.2lf\n", ans);
}
return 0;
}
第二种思路(较快):
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 2005;
struct Line {
int st; //代表1 -1 2种状态
double s, e, x;
bool operator < (const Line & w) const {
return x < w.x; //按照x小的排在前面
}
} line[N];
struct Node {
int cnt; //代表被包含的次数
double len1, len2; //len1代表包含1次的长度 len2代表包含2次以上的长度
} tr[N << 2];
int n, cnt, t; // cnt代表离散化后数组里面元素的个数
double fy[N], x1, x2, y1, y2;
void build(int id, int l, int r) {
tr[id].cnt = tr[id].len1 = tr[id].len2 = 0;
if (l == r) return;
int mid = (l + r) >> 1;
build(id << 1, l, mid);
build(id << 1 | 1, mid + 1, r);
}
void pushup(int id, int l, int r) {
if (tr[id].cnt){ // 代表id这个区间被覆盖了
tr[id].len1 = fy[r + 1] - fy[l]; //因为一个点代表的是一个区间 那r这个点的区间 就是[fy[r], fy[r + 1]]这段长度
} else if (l != r){ //如果这整个区间没有被包含 那么就由儿子区间组成
tr[id].len1 = tr[id << 1].len1 + tr[id << 1 | 1].len1;
} else tr[id].len1 = 0; //叶子结点
//当cnt >= 2的时候
if (tr[id].cnt >= 2) {
tr[id].len2 = fy[r + 1] - fy[l];
} else if (l != r && tr[id].cnt == 1) { //当cnt == 1的时候 子区间若被包含了一次那么加上父亲的一次就是2次了
tr[id].len2 = tr[id << 1].len1 + tr[id << 1 | 1].len1;
} else if (l != r){ //这种情况若不是根节点 那么便可以通过子节点的len2得来
tr[id].len2 = tr[id << 1].len2 + tr[id << 1 | 1].len2;
} else { 根节点 或者 不满足上面条件的都直接等于0
tr[id].len2 = 0;
}
}
void update(int id, int l, int r, int x, int y, int d) {
if (x <= l && r <= y) {
tr[id].cnt += d;
pushup(id, l, r); //及时更新这段区间 因为父亲区间可能需要用到我这个区间的len
return;
}
int mid = (l + r) >> 1;
if (x <= mid) update(id << 1, l, mid, x, y, d);
if (y > mid) update(id << 1 | 1, mid + 1, r, x, y, d);
pushup(id, l, r);
}
int find(double y) {
return lower_bound(fy + 1, fy + 1 + cnt, y) - fy;
}
int main() {
scanf("%d", &t);
while (t--) {
scanf("%d", &n);
for (int i = 1, j = 1; i <= n; i++) {
scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
line[j].s = y1, line[j].e = y2, line[j].x = x1, line[j].st = 1, fy[j++] = y1;
line[j].s = y1, line[j].e = y2, line[j].x = x2, line[j].st = -1, fy[j++] = y2;
}
sort(line + 1, line + 1 + 2 * n);
sort(fy + 1, fy + 1 + 2 * n);
cnt = unique(fy + 1, fy + 1 + 2 * n) - fy - 1; //获取去重后元素的个数
build(1, 1, cnt - 1); //一个点代表的是一个区间, 一共有cnt个点 所以只有cnt-1个区间
double ans = 0;
for (int i = 1; i <= 2 * n; i++) {
ans += tr[1].len2 * (line[i].x - line[i - 1].x);
update(1, 1, cnt - 1, find(line[i].s), find(line[i].e) - 1, line[i].st); //这里-1是因为每个点代表的是一个区间 我们不能包含e那个点所代表的区间
}
printf("%.2lf\n", ans);
}
return 0;
}