扫描线
扫描线
经典问题之求矩形面积并,可以使用线段树和扫描线。
比方说我们要对这俩东西求面积并,我们简单分割一下。
然后扫描线就是,从最下面一条绿线向上扫过去,遇到下底边则加上这个矩形,遇到上底边则减去这个矩形。
回到这道题,发现给了我们矩形的两个角,那么上底边和下底边是好求的。
发现这样对图形分层之后,每部分的高是好求的,就是所有底边排个序两两做差。于是考虑求底的长度。
这几条绿线把
我们对
然后我们依次扫过所有边,显然只有
我们对于每个区间维护四个东西,分别是:这个区间在线段树中的下标范围,当前区间被几个矩形完整覆盖,当前区间被覆盖的长度。
这里简单说一下上传:考虑当前矩形是否被完全覆盖。如果是,那么长度直接为这个区间所能达到的最大长度,否则从两个儿子更新。
代码:
#include<bits/stdc++.h>
#define int long long
#define N 100005
using namespace std;
int n,x[N<<1];
struct line{
int l,r,h,flag;
bool operator<(const line &t)const{
return h<t.h;
}
}l[N<<1];
struct node{
int l,r,sum,len;
//l,r区间左右端点
//sum该区间被几个矩形完全覆盖
//len该区间被覆盖的长度
}tr[N<<4];
//需要开16倍,因为pushup中会访问到这么大的点,虽然这个点啥也没存
void pushup(int u){
int l=tr[u].l,r=tr[u].r;
if(tr[u].sum!=0)tr[u].len=x[r+1]-x[l];
//被完全覆盖则不能用子区间更新,因为子节点没有记录信息
else tr[u].len=tr[u<<1].len+tr[u<<1|1].len;
}
void build(int u,int l,int r){
tr[u]={l,r,0,0};
if(l==r)return;
int mid=l+r>>1;
build(u<<1,l,mid);
build(u<<1|1,mid+1,r);
pushup(u);
//其实pushup没必要,都是0
}
void modify(int u,int L,int R,int add){
int l=tr[u].l,r=tr[u].r;
if(x[r+1]<=L||x[l]>=R)return;
//完全无交,直接返回
if(x[l]>=L&&x[r+1]<=R){
tr[u].sum+=add;
pushup(u);
return;
//把这个区间的线加上/删掉
}
modify(u<<1,L,R,add);
modify(u<<1|1,L,R,add);
pushup(u);
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++) {
int x_1,y_1,x_2,y_2;
cin>>x_1>>y_1>>x_2>>y_2;
x[2*i-1]=x_1;x[2*i]=x_2;
l[2*i-1]={x_1,x_2,y_1,1};
l[2*i]={x_1,x_2,y_2,-1};
}
n<<=1;
sort(x+1,x+n+1);
sort(l+1,l+n+1);
int tot=unique(x+1,x+n+1)-x-1;
build(1,1,tot-1);
//这里[l,r]表示x[l]-x[r+1]
//否则在访问单点时长度为x[l]-x[l]=0,显然不对
//所以tot-1实际到了x[tot]
int res=0;
for(int i=1;i<n;i++){
modify(1,l[i].l,l[i].r,l[i].flag);
res+=tr[1].len*(l[i+1].h-l[i].h);
}
cout<<res;
return 0;
}
矩形周长Picture
首先我们将横纵边分开计算。
考虑纵边,容易发现纵边长度为
这个
考虑横边,容易发现横边长度为
为什么横边这样算呢,因为考虑要么这条边为
代码:
#include<bits/stdc++.h>
#define int long long
#define N 5005
using namespace std;
int n,x[N<<1];
struct line{
int l,r,h,flag;
bool operator<(const line &t)const{
if(h!=t.h)return h<t.h;
return flag>t.flag;
}
}l[N<<1];
struct node{
int l,r,sum,len,cnt,lc,rc;
//l,r区间左右端点
//sum该区间被几个矩形完全覆盖
//len该区间被覆盖的长度
//cnt该区间内有多少条线段
//lc,rc左右两边是否有线段覆盖
}tr[N<<4];
//需要开16倍,因为pushup中会访问到这么大的点,虽然这个点啥也没存
void pushup(int u){
int l=tr[u].l,r=tr[u].r;
if(tr[u].sum!=0){
tr[u].len=x[r+1]-x[l];
tr[u].lc=tr[u].rc=1;
tr[u].cnt=1;
}
//被完全覆盖则不能用子区间更新,因为子节点没有记录信息
else{
tr[u].len=tr[u<<1].len+tr[u<<1|1].len;
tr[u].lc=tr[u<<1].lc;
tr[u].rc=tr[u<<1|1].rc;
tr[u].cnt=tr[u<<1].cnt+tr[u<<1|1].cnt;
if(tr[u<<1].rc&&tr[u<<1|1].lc)tr[u].cnt--;
//两条线段合二为一,需要减掉一条
}
}
void build(int u,int l,int r){
tr[u]={l,r,0,0,0,0,0};
if(l==r)return;
int mid=l+r>>1;
build(u<<1,l,mid);
build(u<<1|1,mid+1,r);
pushup(u);
//其实pushup没必要,都是0
}
void modify(int u,int L,int R,int add){
int l=tr[u].l,r=tr[u].r;
if(x[r+1]<=L||x[l]>=R)return;
//完全无交,直接返回
if(x[l]>=L&&x[r+1]<=R){
tr[u].sum+=add;
pushup(u);
return;
//把这个区间的线加上/删掉
}
modify(u<<1,L,R,add);
modify(u<<1|1,L,R,add);
pushup(u);
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++) {
int x_1,y_1,x_2,y_2;
cin>>x_1>>y_1>>x_2>>y_2;
x[2*i-1]=x_1;x[2*i]=x_2;
l[2*i-1]={x_1,x_2,y_1,1};
l[2*i]={x_1,x_2,y_2,-1};
}
n<<=1;
sort(x+1,x+n+1);
sort(l+1,l+n+1);
int tot=unique(x+1,x+n+1)-x-1;
build(1,1,tot-1);
//这里[l,r]表示x[l]-x[r+1]
//否则在访问单点时长度为x[l]-x[l]=0,显然不对
//所以tot-1实际到了x[tot]
int res=0;
int sum=0;
for(int i=1;i<n;i++){
modify(1,l[i].l,l[i].r,l[i].flag);
res+=abs(sum-tr[1].len);
cout<<sum<<' '<<tr[1].len<<'\n';
sum=tr[1].len;
res+=2*tr[1].cnt*(l[i+1].h-l[i].h);
}
cout<<l[n].r<<' '<<l[n].l<<'\n';
res+=l[n].r-l[n].l;
cout<<res;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】