题目链接:
题目大意:
以对角线的形式给出
n
个矩形,给出左下点
数据范围:
解题思路:
第一次做线段树扫描线的题,方便自己以后忘了好看,所以打算写一写自己的理解。
有图有真相,虽说图很丑,将就一下。
扫描线,顾名思义,就是有一根假象的线在扫描矩形,就像扫二维码的那根线一样,把所有矩形扫一遍,就知道矩形的并面积了。可以上下扫,也可以左右扫,大同小异,我采用的是上下扫。
给3个矩形,如图:
上下扫描是对
x
轴建立线段树,所以与
可以看出,所有矩形的覆盖总区间为
[1,8]
,就对这个区间建立线段树。
然后想象有一根线平行于
x
轴,从下往上扫描。
扫描到一条底边的时候,就将其所对应的区间
[L,R]的flag
修改,表示被覆盖。扫描到顶边的时候,则其对应区间
[L,R]
的覆盖次数减少一次。这时,
flag=±1
的用处就出来了,修改区间的时候直接让其
lazy[]+=flag
就好了。
除此之外,还需要得到总区间的总覆盖长度,代码体现在那个
len
上。
准备工作做完了,开始计算面积。记
L
为总区间的总覆盖长度。
现扫描到第一条边,修改对应区间,并更新得到
L
。令
如图,求得第一部分面积:
此时
H==1,L==3,则ans+=H∗L,即ans+=3
。
紧接着扫描到第二条线,计算第二部分面积,如图:
此时
H==1,L=L1+L2==3+3==6,则ans+=6
。后几部分如图:
第三部分,ans+=7
。
第四部分,ans+=5
。
第五部分,ans+=3
。
由此看出,只用扫描
(n−1)
条边就可以计算出面积并了最后得出
ans==24
。
坐标最多可达
1e5
,所以,离散化一下为好。
另外,我采用的是区间化点法(自创名词)建立的线段树。
再者,推荐两篇比较好的博文:one、two
详见代码:
//线段树--矩形面积求并
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <set>
#include <map>
#include <queue>
#include <stack>
using namespace std;
typedef long long LL;
const int inf = 1 << 30;
const LL INF = 1LL << 60;
const int MaxN = 2005;
int n, m, k, cas;
struct Segment
{
double xl, xr; //分别表示这条横线的左右端点坐标
double h; //这条横线的纵坐标
int flag; //flag为1表示这条横线是矩形的下边,-1就是上边
Segment() {} //这种操作要学到
Segment(double a, double b, double c, int d) : xl(a), xr(b), h(c), flag(d) {}
bool friend operator < (Segment a, Segment b) {
if(a.h == b.h) return a.flag > b.flag;
else return a.h < b.h;
}
}seg[2 * MaxN + 5];
double x[2 * MaxN + 5]; //存离散去重后的横坐标
struct segtree
{
int l, r;
double len;
//当前区间被覆盖的长度,以此回溯得到总区间被覆盖的长度,即tree[1].len
}tree[8 * MaxN + 5];
int lazy[8 * MaxN + 5];
//lazy[rt]表示rt所管辖的区间被覆盖了多少次
void Build(int rt, int l, int r) {
tree[rt].l = l, tree[rt].r = r;
tree[rt].len = 0.0;
lazy[rt] = 0;
if(l == r) return;
int mid = (l + r) >> 1;
Build(rt << 1, l, mid);
Build(rt << 1 | 1, mid + 1, r);
}
int bin_search(double val) { //查找val在x数组中的位置
int l = 1, r = k;
int mid = 0, res = 0;
while(l <= r) {
mid = (l + r) >> 1;
if(x[mid] >= val) res = mid, r = mid - 1;
else l = mid + 1;
}
return res;
}
void push_up(int rt) {
if(lazy[rt] > 0) { //若当前区间被覆盖,则更新该区间的len
tree[rt].len = x[tree[rt].r + 1] - x[tree[rt].l];
return;
}
tree[rt].len = tree[rt << 1].len + tree[rt << 1 | 1].len; //回溯
}
void update(int rt, int L, int R, int f) {
if(L <= tree[rt].l && tree[rt].r <= R) {
lazy[rt] += f;
push_up(rt);
return ;
}
int mid = (tree[rt].l + tree[rt].r) >> 1;
if(L <= mid) update(rt << 1, L, R, f);
if(R > mid) update(rt << 1 | 1, L, R, f);
push_up(rt);
}
int main()
{
cas = 0;
while(scanf("%d", &n) != EOF)
{
if(n == 0) break;
m = 0;
for(int i = 1; i <= n; i++) {
double x1, y1, x2, y2;
scanf("%lf %lf %lf %lf", &x1, &y1, &x2, &y2);
x[++m] = x1;
seg[m] = Segment(x1, x2, y1, 1);
x[++m] = x2;
seg[m] = Segment(x1, x2, y2, -1);
} //m == 2 * n
sort(x + 1, x + m + 1);
sort(seg + 1, seg + m + 1);
k = 1;
for(int i = 2; i <= m; i++) //离散化去重
if(x[i] != x[i + 1])
x[++k] = x[i];
Build(1, 1, k); //建树
double ans = 0.0;
for(int i = 1; i <= m - 1; i++) {
int L = bin_search(seg[i].xl); //查找当前线段左端点在x数组中的位置
int R = bin_search(seg[i].xr) - 1; //同理,查找右端点
update(1, L, R, seg[i].flag); //更新区间
ans += tree[1].len * (seg[i + 1].h - seg[i].h);
}
printf("Test case #%d\n", ++cas);
printf("Total explored area: %.2lf\n", ans);
printf("\n");
memset(x, 0, sizeof(x));
}
return 0;
}
进阶版:矩形交面积