扫描线

扫描线是一种用来解决许多矩形面积/周长并的问题


下面先来说矩形面积并

现在我们有这样四个矩形,我们要求他们的面积并,怎么求呢?我们就需要利用到扫描线算法

我们考虑把每个矩形拆成两条直线,就像这样

然后我们发现我们要求的面积被扫描线分成了这样的几个部分

 

我们可以考虑使用线段树来维护整体被覆盖的长度,同时把扫描线拆成+1-1两种

每次计算的答案就应该是线段树中被覆盖的长度\times (x_i-x_{i-1})

但是线段树怎么维护一段区间被覆盖的长度是多少呢?

这个东西其实利用标记永久化的线段树更加方便

我们给每个节点开两个变量tagsum分别表示这个点被完整覆盖了几次和答案

那么每次更新的时候我们就找到这些区间,让他们的tag加一,然后pushup就可以了

程序写出来大概是这样的

void pushup(int u){
    if(seg[u].tag)seg[u].sum=b[seg[u].r+1]-b[seg[u].l];
    else seg[u].sum=seg[lc].sum+seg[rc].sum;
}

void update(int u,int l,int r,int k){
    if(seg[u].l>=l&&seg[u].r<=r){seg[u].tag+=k,pushup(u);return;}
    int mid=seg[u].l+seg[u].r>>1;
    if(l<=mid)update(lc,l,r,k);
    if(r>mid)update(rc,l,r,k);
    pushup(u);
}

注意因为我们离散化过了,所以应该是之前的值

但是我们会发现这个东西如果我们叶子节点存储的是一个点的话+1-1的判断非常麻烦,所以为了方便,我们应该让每个节点储存的是(l,r)之间的一段区间,这样就不用考虑+1-1的问题了,那么我们的线段树也要相应的进行调整

还有一个问题,如果我们修改的时候修改到了叶子节点,我们之前的程序仍然需要执行一次pushup,可能会访问到更深的地方从而造成RE(在洛谷上是WA),所以有两种解决方法,一是开十六倍空间,二是特判叶子节点的情况

那么最后我们的代码写出来是这个样子滴

#include <bits/stdc++.h>
using namespace std;

# define Rep(i,a,b) for(int i=a;i<=b;i++)
# define _Rep(i,a,b) for(int i=a;i>=b;i--)
# define RepG(i,u) for(int i=head[u];~i;i=e[i].next)

typedef long long ll;

const int N=2e5+5;

template<typename T> void read(T &x){
   x=0;int f=1;
   char c=getchar();
   for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
   for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-'0';
    x*=f;
}

int n;
int b[N];
ll ans;

struct scan{
    int x,y1,y2,opt;
    bool operator < (const scan &cmp)const{
        if(x!=cmp.x)return x<cmp.x;
        return opt>cmp.opt;
    }
}a[N];

struct segment_tree{
    int l,r,sum,tag;
}seg[N<<2];

# define lc (u<<1)
# define rc (u<<1|1)

void pushup(int u){
    if(seg[u].r==seg[u].l+1){
        if(seg[u].tag)seg[u].sum=b[seg[u].r]-b[seg[u].l];
        else seg[u].sum=0;
    }
    else{
        if(seg[u].tag)seg[u].sum=b[seg[u].r]-b[seg[u].l];
        else seg[u].sum=seg[lc].sum+seg[rc].sum;
    }
}

void build(int u,int l,int r){
    seg[u].l=l,seg[u].r=r;
    if(r==l+1)return;
    int mid=l+r>>1;
    build(lc,l,mid);
    build(rc,mid,r);
}

void update(int u,int l,int r,int k){
    if(seg[u].l>=l&&seg[u].r<=r){seg[u].tag+=k,pushup(u);return;}
    int mid=seg[u].l+seg[u].r>>1;
    if(l<mid)update(lc,l,r,k);
    if(r>mid)update(rc,l,r,k);
    pushup(u);
}

signed main()
{
    read(n);
    Rep(i,1,n){
        int x1,x2,y1,y2;
        read(x1),read(y1),read(x2),read(y2);
        b[i]=y1,b[i+n]=y2;
        a[i]=(scan){x1,y1,y2,1};
        a[i+n]=(scan){x2,y1,y2,-1};
    }
    sort(a+1,a+2*n+1);
    sort(b+1,b+2*n+1);
    int sz=unique(b+1,b+2*n+1)-b-1;
    Rep(i,1,2*n){
        a[i].y1=lower_bound(b+1,b+sz+1,a[i].y1)-b;
        a[i].y2=lower_bound(b+1,b+sz+1,a[i].y2)-b;
    }
    build(1,1,sz);
    Rep(i,1,2*n){
        ans+=1ll*seg[1].sum*(a[i].x-a[i-1].x);
        update(1,a[i].y1,a[i].y2,a[i].opt);
    }
    printf("%lld\n",ans);
    return 0;
}

如果大家习惯写标记下传的线段树的话可能不太习惯,但是这道题我们会发现标记永久化的线段树显然更佳好写一些

但是我们注意一下,我们在这道题目中写的是只查询线段树中1号节点的答案,那么这个时候我们是不需要任何特别的操作的,但是我们仔细观察这个做法我们就会发现如果查询的话还按之前的查询方法是会出现问题的,因为一个点下面的点并没有被标记过

所以这个时候如果是区间查询的话我们就需要标记一下路径上是否有被标记过的点,如果有就输出这一段的b_r-b_l

也许将来我会专门写一个标记永久化的博客我回头再说吧,但是上面说的其实都是给下面周长并做铺垫


2020.4.14 20:55 作业写不完了,明天再说周长并吧


2020.4.15 

下面我们来说一下周长并

现在有四个矩形,我们需要求他们的周长并

我们先考虑竖着的怎么计算

我们观察一下竖着的答案是哪一部分

应该是标红的部分

顺着面积并的做法,我们还是把矩形拆成+1-1两种边,仔细观察上面的那张图我们就会发现

对于+1的边,我们的答案就是这条边对应的区间之前没有被覆盖过的长度

对于-1的边,我们的答案就是这条边删掉之后对应的区间没有被覆盖过的长度

但是这个东西貌似不太好维护,而且我们想直接粘上面那道题的代码,怎么办呢?

我们可以把问题转化成区间长度减去被覆盖过至少一次的长度,这样我们就可以直接粘上面的线段树代码了

但是!!!这里涉及到了区间查询的问题,所以我们查询的时候要修改一下正常的查询

横着的边就再做一遍就好了

但是代码想写好看还真的挺难的

#include <bits/stdc++.h>
using namespace std;

# define Rep(i,a,b) for(int i=a;i<=b;i++)
# define _Rep(i,a,b) for(int i=a;i>=b;i--)
# define RepG(i,u) for(int i=head[u];~i;i=e[i].next)

typedef long long ll;

const int N=1e5+5;

template<typename T> void read(T &x){
   x=0;int f=1;
   char c=getchar();
   for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
   for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-'0';
    x*=f;
}

int n;
int b[2][N],szx,szy;
int ans;

struct ScanLine{
    int x,y1,y2,opt;
    bool operator < (const ScanLine &cmp)const{
        if(x!=cmp.x)return x<cmp.x;
        return opt>cmp.opt;
    }
}a[2][N];

struct segment_tree{
    int l,r,tag,sum,siz;
}seg[N<<2];

# define lc (u<<1)
# define rc (u<<1|1)

void pushup(int u){
    if(seg[u].r==seg[u].l+1){
        if(seg[u].tag)seg[u].sum=seg[u].siz;
        else seg[u].sum=0;
    }
    else{
        if(seg[u].tag)seg[u].sum=seg[u].siz;
        else seg[u].sum=seg[lc].sum+seg[rc].sum;
    }
}

void build(int u,int l,int r,int which){
    seg[u].l=l,seg[u].r=r;
    seg[u].tag=seg[u].sum=0;
    seg[u].siz=b[which][r]-b[which][l];
    if(r==l+1)return;
    int mid=l+r>>1;
    build(lc,l,mid,which);
    build(rc,mid,r,which);
}

void update(int u,int l,int r,int k){
    if(seg[u].l>=l&&seg[u].r<=r){seg[u].tag+=k,pushup(u);return;}
    int mid=seg[u].l+seg[u].r>>1;
    if(l<mid)update(lc,l,r,k);
    if(r>mid)update(rc,l,r,k);
    pushup(u);
}

int query(int u,int l,int r,bool down){
    if(seg[u].l>=l&&seg[u].r<=r)return down?seg[u].siz:seg[u].sum;
    int mid=seg[u].l+seg[u].r>>1;
    int res=0;
    if(l<mid)res+=query(lc,l,r,down|seg[u].tag);
    if(r>mid)res+=query(rc,l,r,down|seg[u].tag);
    return res;
}

int main()
{
    read(n);
    Rep(i,1,n){
        int x1,x2,y1,y2;
        read(x1),read(y1),read(x2),read(y2);
        a[0][i]=(ScanLine){x1,y1,y2,1};
        a[0][i+n]=(ScanLine){x2,y1,y2,-1};
        a[1][i]=(ScanLine){y1,x1,x2,1};
        a[1][i+n]=(ScanLine){y2,x1,x2,-1};
        b[0][i]=y1,b[0][i+n]=y2;
        b[1][i]=x1,b[1][i+n]=x2;
    }
    sort(a[0]+1,a[0]+2*n+1);
    sort(a[1]+1,a[1]+2*n+1);
    sort(b[0]+1,b[0]+2*n+1);
    sort(b[1]+1,b[1]+2*n+1);
    szy=unique(b[0]+1,b[0]+2*n+1)-b[0]-1;
    szx=unique(b[1]+1,b[1]+2*n+1)-b[1]-1;
    Rep(i,1,2*n){
        a[0][i].y1=lower_bound(b[0]+1,b[0]+szy+1,a[0][i].y1)-b[0];
        a[0][i].y2=lower_bound(b[0]+1,b[0]+szy+1,a[0][i].y2)-b[0];
        a[1][i].y1=lower_bound(b[1]+1,b[1]+szx+1,a[1][i].y1)-b[1];
        a[1][i].y2=lower_bound(b[1]+1,b[1]+szx+1,a[1][i].y2)-b[1];
    }
    build(1,1,szy,0);
    Rep(i,1,2*n){
        if(a[0][i].opt==1){
            ans+=b[0][a[0][i].y2]-b[0][a[0][i].y1]-query(1,a[0][i].y1,a[0][i].y2,0);
            update(1,a[0][i].y1,a[0][i].y2,a[0][i].opt);
        }
        else{
            update(1,a[0][i].y1,a[0][i].y2,a[0][i].opt);
            ans+=b[0][a[0][i].y2]-b[0][a[0][i].y1]-query(1,a[0][i].y1,a[0][i].y2,0);
        }
    } 
    build(1,1,szx,1);
    Rep(i,1,2*n){
        if(a[1][i].opt==1){
            ans+=b[1][a[1][i].y2]-b[1][a[1][i].y1]-query(1,a[1][i].y1,a[1][i].y2,0);
            update(1,a[1][i].y1,a[1][i].y2,a[1][i].opt);
        }
        else{
            update(1,a[1][i].y1,a[1][i].y2,a[1][i].opt);
            ans+=b[1][a[1][i].y2]-b[1][a[1][i].y1]-query(1,a[1][i].y1,a[1][i].y2,0);
        }
    }  
    printf("%d\n",ans);
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值