今日正题基础应用一面积并
存在这样两个矩形求其面积并。
第一步:我们将所有矩形的横边都画上线,我们的扫描线就从下往上扫 (扫描线一般是从下往少,或者从左往右,当然你学会了也可以有所创新)
思考🤔:这样看的话我们的扫描线扫到①边长度就为截取边的长度就好了,③边就应该是矩形2的横边长。②边怎么办?这就涉及到标记了!
第二步 :每一个矩形的下边权值为1(代表入边),上边权值为-1(出边),结合线段树维护区间,当扫到①时a[1]到a[3]这一段长度更新,扫描线长度a[3]-a[1],当扫到②边a[2]到a[4]更新,a[2]到a[3]标记累计,扫描线长度a[4]-a[1]。当扫到③边a[1]到a[3]更新,扫描线长度a[4]-a[2],有一个发现a[2]到a[3]总共被累计两次,所以即便③边出边了一段a[2]到a[3]但是扫描线的长度还是有这段距离的。基本思路就到这里代码详解。
#include<bits/stdc++.h>
#define put putchar
#define get getchar
#define is isdigit
#define il inline
#define dfor(i,a,b) for(int i=a;i<=b;++i)
#define ls (i<<1)
#define lr (i<<1|1)
using namespace std;
typedef long long ll;
const int N=1e6+10;
int n,a[N];
struct p
{
int l,r,h,flag;
p(){}
p(int l1,int r1,int h1,int f1)
{
l=l1,r=r1,h=h1,flag=f1;
}
}node[N<<1];//用一个结构体来储存横边的信息,横边的左右横坐标,纵坐标和权值
//当我们的扫描线扫到当前横边时就用上面一条横边的纵坐标减去当前横边的纵坐标乘以扫描线截取的长度
//所以我们的node结构体应该从小到大排序根据纵坐标的值
struct p1
{
int l,r,sum,len;
p1(){}
p1(int l1,int rr,int ss,int ee)
{
l=l1,r=rr,sum=ss,len=ee;
}
}tr[N<<2];//这就是我们的线段树了,l,r维护区间的基本操作了,sum就是用来累计标记的,len就是我们
//扫描线截取的长度
bool cmp(p a,p b)
{
return a.h<b.h;
}//node结构体排序
void build(int i,int l,int r)
{
tr[i]=p1(l,r,0,0);
if(l==r) return ;
int mid=l+r>>1;
build(ls,l,mid);
build(lr,mid+1,r);
}//建树基操
il void push(int i)
{
if(tr[i].sum) tr[i].len=a[tr[i].r+1]-a[tr[i].l];
else tr[i].len=tr[ls].len+tr[lr].len;
}//当一个区间被标记完全说明当前区间完全处于扫描线正在扫描的横边内,直接区间横坐标之差计算了
//没有被标记,当前区间的len就为左右儿子的len之和
void modify(int i,int L,int R,int k)
{
if(L<=a[tr[i].l]&&a[tr[i].r+1]<=R)//下面详解
{
tr[i].sum+=k;
push(i);
return ;
}
if(L<=a[tr[ls].r]) modify(ls,L,R,k);
if(R>a[tr[lr].l]) modify(lr,L,R,k);//R>=a[tr[lr].l+1]==R>a[tr[lr].l]
push(i);
}
当前扫描线扫瞄到的横边为横坐标分别为(1,8) ,所以呢?更新线段树的条件还是原来的左右端点分别对应吗?答案是:否!可以发现线段树某一个区间(l,r)对应上坐标系的区间维护的其实是(l,r+1),所以?当我们抛出一个坐标系区间去更新线段树时,线段树的右端点应该加上1去对应坐标系区间!!!正解 下面完整代码👇
#include<bits/stdc++.h>
#define put putchar
#define get getchar
#define is isdigit
#define il inline
#define dfor(i,a,b) for(int i=a;i<=b;++i)
#define ls (i<<1)
#define lr (i<<1|1)
using namespace std;
typedef long long ll;
const int N=1e6+10;
int n,a[N];
struct p
{
int l,r,h,flag;
p(){}
p(int l1,int r1,int h1,int f1)
{
l=l1,r=r1,h=h1,flag=f1;
}
}node[N<<1];
struct p1
{
int l,r,sum,len;
p1(){}
p1(int l1,int rr,int ss,int ee)
{
l=l1,r=rr,sum=ss,len=ee;
}
}tr[N<<2];
bool cmp(p a,p b)
{
return a.h<b.h;
}
void build(int i,int l,int r)
{
tr[i]=p1(l,r,0,0);
if(l==r) return ;
int mid=l+r>>1;
build(ls,l,mid);
build(lr,mid+1,r);
}
il void push(int i)
{
if(tr[i].sum) tr[i].len=a[tr[i].r+1]-a[tr[i].l];
else tr[i].len=tr[ls].len+tr[lr].len;
}
void modify(int i,int L,int R,int k)
{
if(L<=a[tr[i].l]&&a[tr[i].r+1]<=R)
{
tr[i].sum+=k;
push(i);
return ;
}
if(L<=a[tr[ls].r]) modify(ls,L,R,k);
if(R>a[tr[lr].l]) modify(lr,L,R,k);
push(i);
}
int main()
{
cin>>n;
int x,xx,y,yy;
for(int i=1;i<=n;++i)
{
cin>>x>>y>>xx>>yy;
a[ls]=x,a[lr-2]=xx;//a数组记录横坐标主要用于离散化,因为数据范围开不了那么大数组
//ls==(i<<1)==i*2,lr==(i<<1|1)==i*2+1,一条线段两个横坐标
node[ls]=p(x,xx,y,1),node[lr-2]=p(x,xx,yy,-1);//node结构体记录每条横边信息
}
n<<=1;//上面记录的都是两倍的信息了,直接放大方便些
sort(a+1,a+n+1);
sort(node+1,node+n+1,cmp);
int tot=unique(a+1,a+n+1)-a-1;//不去重区间的对应关系会出错
//试想1 2 3 3 3 4 我们要更新1-4,本应该4和4比,但是数组4的位置时3,这不就错了吗
//有的是用二分查找,你查找4的位置不去重找出来的是6,这不也错了吗
build(1,1,tot-1);//为什么tot-1?还是更新1-4,我建树1-3,我更新时是不是右端点要+1
//并且我们的比较还是R和a[tr[i].r+1]的比较,3+1等于4,R和a[4]比较
ll ans=0;
for(int i=1;i<n;++i)//最后一条边不用管了我们用的上面一条边的纵坐标减去下面一条边的纵坐标
//你假设只有一个矩形就懂了
{
modify(1,node[i].l,node[i].r,node[i].flag);//1是基操,l,r对应区间,flag我们的标记
ans+=tr[1].len*(ll)(node[i+1].h-node[i].h);//底乘高这。。。
}
cout<<ans;
return 0;
}