关于线段树应用的一些问题
矩型周长并
- 今天做到求矩形合并面积的一道同类题——求矩形组的总周长和,但是……它好像和上课说的矩形面积并操作有些小小的不同……
- 这是初始给出数据所得到的一幅图,由图中可以看到会有两种情况:
- 1、图中的矩形会有一块覆盖在另外一块上,但是这种情况下周长是不用计算的。(如下图)
- 2、图中的多块矩形相交后会有中空部分要求多算长度
- 以上两种情况都会对离散化有影响,这个方面真的想了好久好久好久……
- 就是这幅图要求的最终的结果。
算法
- 类似于求面积并,我们任然使用扫描线的方法。
- 首先从下往上进行离散化处理(这个操作详见代码,比较简单),再从下往上进行扫描。
- 一维使用线段树进行维护,但由于有竖直上的边需要添加,那么就需要判断边是否有被覆盖或者是否有重合。
- 由于有中空和覆盖的条件,那么就要用vrl数组和lbd,rbd来进行判断是否有左右边界,如果子树都具有左右边界,而父亲原本的vtl就具有2,那么就需要将这一个节点的vtl先减去2再加上左右子节点所具有的总共的边界个数。
- vtl用于计算最终的竖直上的长度,但由于有覆盖和中空的两种情况,所以引入lbd和rbd用于判断
- last用于即时更新最终结果和上一次结果的差异(这个地方讲的有点小烦还是直接看代码吧)。
以下是实现的程序
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int MAXN=1e5,N=2e4+10;
struct node{
int l,r,h,d;//l,r记录边的左右节点,h记录边所在的高度位置,d记录是最终的出边还是入边,出边为-1,入边为1
node(){}
node(int l,int r,int h,int d):l(l),r(r),h(h),d(d){}
bool operator <(const node s) const{
return h<s.h;//这里按照h来进行排序从小到大搜索出入从而计算
}
}a[N];
int sum[N<<2],cnt[N<<2],vtl[N<<2];//vtl用于记录出入边的左右两端边界(就是竖直上的边的个数)计数,cnt类似于一般的lazy_tag函数,滞后处理
bool lbd[N<<2],rbd[N<<2];//用于判断是否左右子树的两边有边界
int n,minn=MAXN,maxx=-MAXN;
void push_up(int l,int r,int rt){
if(cnt[rt]){//如果cnt[rt]有值,这一步肯定是完全被覆盖需要更新,当中不存在被挖掉的部分(其实这里是push_down)
lbd[rt]=rbd[rt]=true;//更新左右边界
sum[rt]=r+1-l;//结果就要更新成这一条线段存的长度
vtl[rt]=2;//初始是这一段左右两边
}else if(l==r){
sum[rt]=vtl[rt]=lbd[rt]=rbd[rt]=0;//如果是到了单个点的情况那么就不会有左右子节点,全部都是0
}else{//不存在更新,那么将从子节点更新上来
lbd[rt]=lbd[rt<<1];
rbd[rt]=rbd[rt<<1|1];//左右的边界赋值到父节点
sum[rt]=sum[rt<<1]+sum[rt<<1|1];//重新更新父节点的值
vtl[rt]=vtl[rt<<1]+vtl[rt<<1|1];//边界个数要左右更新
if(rbd[rt<<1]&&lbd[rt<<1|1]) vtl[rt]-=2;//如果左孩子的右边界和右孩子的左边界都存在,那么这条中间一定是有被挖掉的,但算一整条不需要记录(因为孩子节点会处理)所以需要剪掉
}
}
void Add(int L,int R,int v,int l,int r,int rt){
if(L<=l&&r<=R){//如果查询范围包含所找的范围那么就在这一段停止吧!!!
cnt[rt]+=v;//这里和求面积并的操作一样,进边加一,出边减一
push_up(l,r,rt);//由于这一段父节点已经被计算过了那么左右子节点也需要及时的更新
return;
}
int mid=(l+r)>>1;
if(L<=mid)Add(L,R,v,l,mid,rt<<1);
if(R>mid)Add(L,R,v,mid+1,r,rt<<1|1);//正常线段树左右搜索
push_up(l,r,rt);//正常回归操作
}
void init(){
scanf("%d",&n);
for (int i=1;i<=n;i++){
int x1,x2,y1,y2;
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
minn=min(minn,x1);//找到该图的矩形最左边与最右边的边界值
maxx=max(maxx,x2);
a[i]=node(x1,x2,y1,1);
a[i+n]=node(x1,x2,y2,-1);//下边为入边,上边为出边
}
n<<=1;//共有2n条边就在这里进行扩倍咯。
sort(a+1,a+1+n);//正常排序操作离散化
}
int main(){
init();
int ans=0,last=0;//起始初始化
for (int i=1;i<=n;i++){
if(a[i].l<a[i].r) Add(a[i].l,a[i].r-1,a[i].d,minn,maxx-1,1);//因为最后面的每一个长度都是r-l+1那么干脆在这里就直接减掉了
ans+=vtl[1]*(a[i+1].h-a[i].h);//增加竖边的值
ans+=abs(sum[1]-last);//增加横边的值(-last是去掉重复覆盖部分)
last=sum[1];//由于sum[1]在内部push_up被重新更新,所以最后要重新更新last,代表着上一条线段的值
}
printf("%d\n",ans);
return 0;
}