题意:
给出若干个与轴平行的矩形,求被覆盖了2次及以上区域的面积
分析:
简述扫描线算法:
下图3个矩形共有6条扫描线,我们只需要计算出相邻两条扫描线之间的被覆盖的面积相加即可得到矩形的面积并,实现如下:
(1)对图中的每条横边(竖边也行)按它的高度从小到大排序
(2)从下到上依次扫描每条横边(即扫描线),计算当前扫描线覆盖的总长度,乘上与上一条扫描线之间的高度差即得到它们之间的被覆盖的面积
(3)第二条扫描线覆盖的长度=第一条横线的长度+第二条横线的长度,所以要用线段树维护当前被覆盖的总长度,当一个矩形的上边扫过后,那么它底边的贡献就没了,所以对每个矩形的底边标记为1,上边标记为-1,方便线段树维护
(4)涉及到线段树,一般需要离散化坐标,同时将边权转化为点权
对于此题,只需要每次计算当前被覆盖2次及以上的线段长度即可
代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+15;
int tg[maxn<<1],n,m;
double x,y,xx,yy,c[maxn],tr2[maxn<<1],tr1[maxn<<1];
/*
tg[x]标记线段树上x节点所代表的区间被完整的覆盖了多少次
tr1[x]记录线段树上x节点所代表的区间被覆盖了1次的长度
tr2[x]记录线段树上x节点所代表的区间被覆盖了2次及以上的长度
*/
struct segement{
double l,r,h;
int f;
}s[maxn<<1];
bool cmp(segement a,segement b){
return a.h < b.h;
}
inline void pushup(int x,int l,int r){
if(tg[x] >= 2) {
tr2[x] = c[r+1] - c[l]; //边权转点权,r+1
tr1[x] = 0;
}
else{
tr2[x] = tr2[x<<1] + tr2[x<<1|1];
if(tg[x] == 1){
tr2[x] += tr1[x<<1] + tr1[x<<1|1];
tr1[x] = c[r+1] - c[l] - tr2[x];
}
else tr1[x] = tr1[x<<1] + tr1[x<<1|1];
}
}
void Updata(int l,int r,int L,int R,int x,int op){
if(l > R || r < L) return;
if(l >= L && r <= R){
tg[x] += op;
pushup(x,l,r);
return ;
}
int mid = (l+r) >> 1;
Updata(l,mid,L,R,x<<1,op);
Updata(mid+1,r,L,R,x<<1|1,op);
pushup(x,l,r);
}
int main(){
int T;cin >> T;
while(T--){
scanf("%d",&n);
int len = 0;
for(int i = 0;i < n; ++i){
cin >> x >> y >> xx >> yy;
s[len].l = s[len+1].l = x;
s[len].r = s[len+1].r = xx;
s[len].h = y;s[len+1].h = yy;
s[len].f = 1;s[len+1].f = -1;
c[len] = x;c[len+1] = xx;
len += 2;
}
sort(c,c+len);
sort(s,s+len,cmp);
int LEN = unique(c,c+len) - c;
double res = 0.0;
for(int i = 0;i < len; ++i){
int L = lower_bound(c,c+LEN,s[i].l) - c;
int R = lower_bound(c,c+LEN,s[i].r) - c;
Updata(0,LEN-1,L,R-1,1,s[i].f); //边权转点权,r-1
if(i < len-1) res += (s[i+1].h-s[i].h)*tr2[1];//根节点tr2[1]就是被覆盖2次及以上的总长度
}
printf("%.2f\n",res);
}
return 0;
}