problem
给你N个矩形,求这些矩阵的面积并 N ≈ 1e5
思路
“扫描线思路”+离散化+线段树维护
关于扫描线:可以看下这篇博客
关于离散化
由于坐标范围较大,需要使其更“紧密”,这样才能用线段树处理
离散化的核心思想就是 相对大小不变 且能通过现在的值确定之前的值
所以离散化的方法就是(以y轴坐标为例) 将y轴坐标塞入id数组(2*n个) 排序
将之前每个事件的y轴坐标替换为在id数组中的下标 (二分去找) 这样就完成了离散化
关于线段树维护
将事件分成两类,即入事件和出事件。
对于入事件,我们将区间cov值+1,即对应区间标记+1
对于出事件,我们将区间cov值-1 ,即对应区间标记-1
每一次处理过一个事件后,对于总区间,我们维护的sum[1]的值为那些标记不为0的区间所对应的长度,这些长度可以作为面积并的长,高即为 evt[i+1].h−evt[i].h
当再处理一个事件时,如果它是出事件,那么自然会更新对应区间cov的值-1,剩下的不为0的区间又可以作为面积并的长,以此类推……
两种不同的写法
注意到,这里处理的是区间,即最小单位是长度为1的区间(离散化后)。但是一般的线段树其叶子节点就表示那个点,而不是区间。
举个例子,比如[3,4]区间+1,一般写法会将叶子节点3和叶子节点4的值+1,再pushup。但这里我们没法处理这样跨结点的区间,因为要么两边都加(超了),要么都return (少了)。即对于点线段树递归区间时要采用左闭右开的方法。
所以有两种写法:
- ①点线段树,需要左闭右开处理
- ②区间线段树,叶子节点可表示区间,需要改变递归区间的边界
下面是两种方法的代码实现,细节见代码注释
代码示例1(点线段树)
//沿y轴方向扫描 对x轴坐标离散化后用线段树维护
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1//这里叶子节点还是表示点而不是区间,这也是通常的做法
using namespace std;
typedef long long ll;
const int maxn=100010*2;//注意"事件是矩形数量的两倍"
int n;
int cov[maxn<<2];//区间覆盖情况
double id[maxn],sum[maxn<<2];//id用于离散化
struct event{
double l,r,h;//h为纵坐标,扫描线平行于x轴
int f;
event(){}//必须要加
event(double l,double r,double h,int f):l(l),r(r),h(h),f(f){}
bool operator < (const event & rhs) const{//按照h进行排序
return h < rhs.h;
}
}evt[maxn];
void pushup(int l,int r,int rt){
//l r这个区间对应的rt 若cov[rt]!=0 也即>=1 当然其不可能<0
if(cov[rt]) sum[rt]=id[r+1]-id[l];//左闭右开 注意是r+1
else if(l==r) sum[rt]=0;//注意点是没有长度的
else sum[rt]=sum[rt<<1]+sum[rt<<1|1];//不是叶子节点,从下往上更新
}
void update(int L,int R,int c,int l,int r,int rt){
if(L<=l && r<=R){//当前节点区间在目的区间内
cov[rt]+=c;
pushup(l,r,rt);
return ;
}
int m=(l+r)>>1;
if(L<=m) update(L,R,c,lson);
if(R>m) update(L,R,c,rson);
pushup(l,r,rt);
}
int main()
{
int cas=1;
while(~scanf("%d",&n),n){
for(int i=1;i<=n;++i){
double x1,y1,x2,y2;//左上和右下
scanf("%lf %lf %lf %lf",&x1,&y1,&x2,&y2);
evt[i]=event(x1,x2,y1,-1);
evt[i+n]=event(x1,x2,y2,1);
id[i]=x1;
id[i+n]=x2;
}
sort(evt+1,evt+2*n+1);
sort(id+1,id+2*n+1);
int m=unique(id+1,id+2*n+1)-id-1;//其实可以不去重
memset(cov,0,sizeof(cov));
memset(sum,0,sizeof(sum));
double ans=0;
for(int i=1;i<2*n;i++){
int l=lower_bound(id+1,id+m+1,evt[i].l)-id;//注意线段树下标从1开始
int r=lower_bound(id+1,id+m+1,evt[i].r)-id;
if(l<=(r-1)) update(l,r-1,evt[i].f,1,m,1);//注意是r-1 套路
ans+=sum[1]*(evt[i+1].h-evt[i].h);//sum[1]记录了当前的“有效长度”
}
printf("Test case #%d\nTotal explored area: %.2f\n\n",cas++, ans);
}
return 0;
}
代码示例2(区间线段树) 注意第7行 、第39行
//1是沿y轴方向扫描 对x轴坐标离散化后用线段树维护
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define lson l,m,rt<<1
#define rson m,r,rt<<1|1//这里叶子节点表示最小区间
using namespace std;
typedef long long ll;
const int maxn=100010*2;//注意"事件是矩形数量的两倍"
int n;
int cov[maxn<<2];//区间覆盖情况
double id[maxn],sum[maxn<<2];//id用于离散化
struct event{
double l,r,h;//h为纵坐标,扫描线平行于x轴
int f;
event(){}//必须要加
event(double l,double r,double h,int f):l(l),r(r),h(h),f(f){}
bool operator < (const event & rhs) const{//按照h进行排序
return h < rhs.h;
}
}evt[maxn];
void pushup(int l,int r,int rt){
//l r这个区间对应的rt 若cov[rt]!=0 也即>=1 当然其不可能<0
sum[rt]=sum[rt<<1]+sum[rt<<1|1];
if(cov[rt]) sum[rt]=id[r]-id[l];//注意是r就行 因为是区间线段树 比较自然
}
void update(int L,int R,int c,int l,int r,int rt){
if(L<=l && r<=R){//当前节点区间在目的区间内
cov[rt]+=c;
pushup(l,r,rt);
return ;
}
int m=(l+r)>>1;
if(L<m) update(L,R,c,lson);//注意这里是< 没有等于号
if(R>m) update(L,R,c,rson);
pushup(l,r,rt);
}
int main()
{
int cas=1;
while(~scanf("%d",&n),n){
for(int i=1;i<=n;++i){
double x1,y1,x2,y2;//左上和右下
scanf("%lf %lf %lf %lf",&x1,&y1,&x2,&y2);
evt[i]=event(x1,x2,y1,-1);
evt[i+n]=event(x1,x2,y2,1);
id[i]=x1;
id[i+n]=x2;
}
sort(evt+1,evt+2*n+1);
sort(id+1,id+2*n+1);
int m=unique(id+1,id+2*n+1)-id-1;//其实可以不去重
memset(cov,0,sizeof(cov));
memset(sum,0,sizeof(sum));
double ans=0;
for(int i=1;i<2*n;i++){
int l=lower_bound(id+1,id+m+1,evt[i].l)-id;//注意线段树下标从1开始
int r=lower_bound(id+1,id+m+1,evt[i].r)-id;
update(l,r,evt[i].f,1,m,1);//注意是r 比较自然
ans+=sum[1]*(evt[i+1].h-evt[i].h);//sum[1]记录了当前的“有效长度”
}
printf("Test case #%d\nTotal explored area: %.2f\n\n",cas++, ans);
}
return 0;
}
这里都是沿y轴方向扫描 对x轴坐标离散化后用线段树维护的;当然也可以反过来