线段树应用练习

扫描线

P5490【模板】扫描线 & 矩形面积并

(并非板子题

有人说它是板子题,但我认为它比二维数点复杂多了

首先考虑最暴力的暴力,我们暴力用矩形覆盖每一个点,然后再统计哪些点被覆盖了

然后我们采用二维数点的思想,对 x 一维进行从小到大模拟,这样对于 y 轴就是一个序列,当 (x,y) 对应的点有区间覆盖时,那我们枚举到第 x 位,序列下标为 y 的位置就要覆盖为 1,然后求出扫描到 x 时,序列上有多少 y 为 1,线段树维护即可

考虑 x,y<=1e9 所以首先我们可以将 x 这一维离散化,因为只有矩形的边才能对序列产生影响

其次怎么处理 y ,也可以将其离散化,排序后去重的数组设为 a,维护一个 b 数组,b[i]=a[i+1]a[i],简单来讲,我们离散化后将 y 这一维分成了一小段一小段,然后 b[i] 就表示第 i 段的长度,我们线段树的每一个叶子节点就是维护这个小段的情况

接下来问题简化成了:

我们有一个序列,最开始次数都为0,每个点有权值和一个次数,(权值就是这个节点代表的段的长度是不会变的,所有操作都针对次数),然后我们会有 n 次区间加 1 (表示为 1 操作),n 次区间减 1 (表示为 2 操作),然后可以保证每一次 2 操作都有且仅有一个 1 操作的区间对应,并且所对应的 1 操作总在 2 操作前进行,然后每一次操作前需要求出来整个区间所有次数大于 0 的点的权值和

这里要是朴素的线段树复杂度会炸,这里用到了一种不下传标记

对于一次修改操作,我们先将这个大区间拆分成 log 个小区间,对于小区间的次数进行 +1 / -1,如果小区间的次数大于 0,那么我们就知道这个区间所有点都选择了,那么权值和即为这个区间所有点权值之和;反之,我们就将权值和设为其左右两个子区间权值和之和,因为在修改时我们只修改有关区间,所以对于子区间和父区间它们的次数都是独立的,最后注意特判区间无法拆分也就是叶子节点的情况

为什么可以使用不下传标记呢,两点:

  1. 每一个 2 操作在前面都能找到 1 操作与其对应,也就是当我们进行区间减时,我们和之前加操作拆分出来的区间是相同的,拆分出来的每个区间都有之前加的标记,而每一个加的标记又恰好能在这次删除操作被删除
  2. 我们只查询最大的区间,考虑如果要查询一个小区间,那么我们在它上面大区间的标记就不会影响到下边,答案就不正确

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int n,cntx,cnty,lenx,leny,ans;
int x1[N],x2[N],Y1[N],y2[N],ax[N],ay[N],numx[N],numy[N];
map<int,int>mpx,mpy;
struct edge{
    int l,r,op;
};
vector<edge>e[N];
struct tree{
    int num,sum,cnt;
}tr[N];
void build(int k,int l,int r){
    if(l==r){
        tr[k].num=numy[l];
        return;
    }
    int mid=(l+r)>>1;
    build(k*2,l,mid);
    build(k*2+1,mid+1,r);
    tr[k].num=tr[k*2].num+tr[k*2+1].num;
}
void change(int k,int l,int r,int z){
    tr[k].cnt+=z;
    if(tr[k].cnt)  tr[k].sum=tr[k].num;
    else if(l==r)  tr[k].sum=0;
    else  tr[k].sum=tr[k*2].sum+tr[k*2+1].sum;
}
void longchange(int k,int l,int r,int x,int y,int z){
    if(x<=l&&r<=y){
        change(k,l,r,z);
        return;
    }
    int mid=(l+r)>>1;
    if(x<=mid)  longchange(k*2,l,mid,x,y,z);
    if(y>mid)  longchange(k*2+1,mid+1,r,x,y,z);
    change(k,l,r,0);
}
signed main(){
    scanf("%lld",&n);
    for(int i=1;i<=n;i++){
        scanf("%lld%lld%lld%lld",&x1[i],&Y1[i],&x2[i],&y2[i]);
        ax[++cntx]=x1[i],ax[++cntx]=x2[i];
        ay[++cnty]=Y1[i],ay[++cnty]=y2[i];
    }
    sort(ax+1,ax+1+cntx);
    sort(ay+1,ay+1+cnty);
    lenx=unique(ax+1,ax+1+cntx)-ax-1;
    leny=unique(ay+1,ay+1+cnty)-ay-1;
    for(int i=1;i<=lenx;i++){
        mpx[ax[i]]=i;
        if(i>1)  numx[i]=ax[i]-ax[i-1];
    }
    for(int i=1;i<=n;i++){
        x1[i]=mpx[x1[i]];
        x2[i]=mpx[x2[i]];
        e[x1[i]].push_back({Y1[i],y2[i],1});
        e[x2[i]].push_back({Y1[i],y2[i],0});
    }
    for(int i=1;i<=leny;i++){
        mpy[ay[i]]=i;
        numy[i]=ay[i+1]-ay[i];
    }
    for(int i=1;i<=lenx;i++){
        for(int j=0;j<e[i].size();j++){
            int l=e[i][j].l,r=e[i][j].r,op=e[i][j].op;
            e[i][j]={mpy[l],mpy[r]-1,op};
            // printf("%lld %lld %lld\n",e[i][j].l,e[i][j].r,e[i][j].op);
        }
    }
    leny--;
    build(1,1,leny);
    for(int i=1;i<=lenx;i++){
        ans+=numx[i]*tr[1].sum;
        for(auto j:e[i]){
            if(j.op==1)  longchange(1,1,leny,j.l,j.r,1);
            else  longchange(1,1,leny,j.l,j.r,-1);
        }
    }
    printf("%lld",ans);
}

P1856 [IOI 1998 ] [USACO5.5] 矩形周长Picture

可以把其转化为矩形面积并相同的做法

首先可以将答案拆分成:从左到右垂直于 y 轴的入边(就是原来不在矩形中,过了这条边就进入了矩形)总长 *2 + 从下到上垂直于 x 轴的入边总长 *2

然后其余的都没有变,我们每加入一条边,就求一下序列总和,如果总和相比于上次增加了,就相当于是加上了一条入边,就加上增加了多少的贡献,反之则不加

注:考虑当两个矩形入边和出边重合的情况,所以假如我们是枚举 x,线段树维护 y,那么当 x 相同时,我们先处理加边,反之也是一样

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e4+5;
int n,cntx,cnty,m,ans;
int X1[N],X2[N],Y1[N],Y2[N],ax[N],ay[N],numx[N],numy[N];
map<int,int>mpx,mpy;
struct edge{
    int l,r,op;
};
vector<edge>e[N];
struct tree{
    int num,sum,cnt;
}tr[N];
bool cmp(edge x,edge y){
    return x.op>y.op;
}
void build(int k,int l,int r,int z){
    tr[k]={0,0,0};
    if(l==r){
        if(z==1)  tr[k].num=numy[l];
        if(z==2)  tr[k].num=numx[l];
        return;
    }
    int mid=(l+r)>>1;
    build(k*2,l,mid,z);
    build(k*2+1,mid+1,r,z);
    tr[k].num=tr[k*2].num+tr[k*2+1].num;
}
void change(int k,int l,int r,int z){
    tr[k].cnt+=z;
    if(tr[k].cnt)  tr[k].sum=tr[k].num;
    else if(l==r)  tr[k].sum=0;
    else  tr[k].sum=tr[k*2].sum+tr[k*2+1].sum;
}
void longchange(int k,int l,int r,int x,int y,int z){
    if(x<=l&&r<=y){
        change(k,l,r,z);
        return;
    }
    int mid=(l+r)>>1;
    if(x<=mid)  longchange(k*2,l,mid,x,y,z);
    if(y>mid)  longchange(k*2+1,mid+1,r,x,y,z);
    change(k,l,r,0);
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d%d%d%d",&X1[i],&Y1[i],&X2[i],&Y2[i]);
        ax[++cntx]=X1[i];ax[++cntx]=X2[i];
        ay[++cnty]=Y1[i];ay[++cnty]=Y2[i];
    }
    sort(ax+1,ax+1+cntx);
    sort(ay+1,ay+1+cnty);
    int lenx=unique(ax+1,ax+1+cntx)-ax-1;
    int leny=unique(ay+1,ay+1+cnty)-ay-1;
    for(int i=1;i<=lenx;i++){
        mpx[ax[i]]=i;
        numx[i]=ax[i+1]-ax[i];
    }
    for(int i=1;i<=leny;i++){
        mpy[ay[i]]=i;
        numy[i]=ay[i+1]-ay[i];
    }
    for(int i=1;i<=n;i++){
        e[mpx[X1[i]]].push_back({mpy[Y1[i]],mpy[Y2[i]]-1,1});
        e[mpx[X2[i]]].push_back({mpy[Y1[i]],mpy[Y2[i]]-1,-1});
    }
    m=leny-1;
    build(1,1,m,1);
    int lst=0;
    for(int i=1;i<=lenx;i++){
        sort(e[i].begin(),e[i].end(),cmp);
        for(auto j:e[i]){
            longchange(1,1,m,j.l,j.r,j.op);
            ans+=max(tr[1].sum-lst,0);
            lst=tr[1].sum;
        }
        e[i].clear();
    }
    for(int i=1;i<=n;i++){
        e[mpy[Y1[i]]].push_back({mpx[X1[i]],mpx[X2[i]]-1,1});
        e[mpy[Y2[i]]].push_back({mpx[X1[i]],mpx[X2[i]]-1,-1});
    }
    m=lenx-1;
    build(1,1,m,2);
    lst=0;
    for(int i=1;i<=leny;i++){
        sort(e[i].begin(),e[i].end(),cmp);
        for(auto j:e[i]){
            longchange(1,1,m,j.l,j.r,j.op);
            ans+=max(tr[1].sum-lst,0);
            lst=tr[1].sum;
        }
        e[i].clear();
    }
    printf("%d",ans*2);
}
posted @   daydreamer_zcxnb  阅读(5)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
点击右上角即可分享
微信分享提示