线段树扫描线(面积并、周长并)例题

扫描线问题

概述

算法核心:扫描。即有一根线从左侧扫描到右侧,在扫描过程中可以统计一些信息。其中对于扫描的每个x,存在垂直x轴的线段,线段信息用线段树维护(注意端点)。

配套使用:离散化(y轴数据范围很大时候,不可能建立很大的线段树)、二分(查找离散化结果)、懒标记(对区间修改)

注意:周长并和面积并的懒标记不需要下传(等同于cnt区间覆盖的次数)

acwing 248. 窗内的星星

思路

  1. 因为我们已经知道了矩形的长和宽,所以对于任意一个星星(x, y, c), 都可以得到一个确切的矩形,所以每一个星星,我们建立一个固定大小的矩形,矩形的左下角为(x, y), 因为边界上的星星不算,所以右上角表示为(x + w, y + h - 1),目标是求出固定矩形区域内的最大值
  2. 所以这道题我们可以用扫描线来做,取出每个区域的左右边界,保存俩个四元组(x, y, y + h - 1, c), (x + w, y, y + h - 1, -c)并将这些四元组按照x坐标排序
  3. 同时用线段树来维护纵坐标的区间最大值, 逐一扫描四元组(x, y1, y2, c), 并执行线段树的修改操作,将[y1, y2]区间上的值都加上c, 不断向上更新父节点最大值,然后得到根节点的dat值得到最终的答案

小结:

build一个空的线段树

将处理好的每个边依次动态加入到线段树中

根据每个边的信息修改线段树。即:入边使区间加上权重,出边减去权重

维护线段树的最大值,每次需要知道全部区间中所有权重的最大值(也就是星星的数量)

代码

#include <bits/stdc++.h>
#define endl "\n"
#define debug(x) cout << #x << ": -----> " << x << endl;
typedef long long ll;
// typedef unsigned long long ull;
#define root tr[u]
#define ls tr[u<<1]
#define rs tr[u<<1|1]
using namespace std;

const int N=1e7+10;
int n,w,h;
struct node{
    int l,r;
    ll data,add; //data为数量信息(cnt),add为懒标记
} tr[8*N];

struct edge{
    int x,y1,y2;
    int k; //每个边的权重,入为正,出为负
    bool operator<(const edge& m) const{ //根据x排序
        if(x==m.x) return k<m.k;
        return x<m.x;
    }
} seg[N*2];
vector<int> mp; //离散化

void pushup(int u){ //更新维护的区间信息最大值
    root.data=max(ls.data,rs.data);
}

void pushdown(int u){ //将标记向下传递
    rs.data+=root.add;
    ls.data+=root.add;
    rs.add+=root.add;
    ls.add+=root.add;
    root.add=0;
}

void build(int u,int l,int r){
    root={l,r,0,0};
    if(l==r) return;
    else{
        int mid=l+r>>1;
        build(u<<1,l,mid),build(u<<1|1,mid+1,r);
        //空树不用pushup
    }
}

void modify(int u,int l,int r,int add){
    if(root.l>=l&&root.r<=r){
        root.add+=add; //包含在区间中时,执行难修改操作
        root.data+=add; //注意:root信息为自己的,懒标记只代表子节点信息
        return;
    }
    else{
        if(root.add) pushdown(u); //有标记要先pushdown
        int mid=root.l+root.r>>1;
        if(l<=mid) modify(u<<1,l,r,add);
        if(r>mid) modify(u<<1|1,l,r,add);
        pushup(u); //更新
    }
}

int main(){
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int t;cin>>t;
    while(t--){
        cin>>n>>w>>h;
        int cnt=0;
        mp.clear(); //艹,调了1h的坑
        mp.push_back(-0x3f3f3f3f);
        for(int i=0;i<n;i++){
            int x,y,c; cin>>x>>y>>c;
            seg[cnt++]={x,y,y+h-1,c};
            seg[cnt++]={x+w,y,y+h-1,-c};
            mp.push_back(y); mp.push_back(y+h-1);
        }
        sort(mp.begin(),mp.end());
        mp.erase(unique(mp.begin(),mp.end()),mp.end());
        int num=mp.size()-1;
        for(int i=0;i<cnt;i++){ //对区间长度没有特别的要求时,这个很方便
            seg[i].y1=lower_bound(mp.begin(),mp.end(),seg[i].y1)-mp.begin();
            seg[i].y2=lower_bound(mp.begin(),mp.end(),seg[i].y2)-mp.begin();
        }
        sort(seg,seg+cnt);//按顺序写入边

        build(1,1,num);

        ll ans=0;
        for(int i=0;i<num;i++){
            modify(1,seg[i].y1,seg[i].y2,seg[i].k);
            ans=max(ans,tr[1].data);
        }
        cout<<ans<<endl;
    }
    return 0;
}

acwing 247. 亚特兰蒂斯

大致思路同上

代码

#include <bits/stdc++.h>
#define endl "\n"
#define debug(x) cout << #x << ": -----> " << x << endl;
// typedef long long ll;
// typedef unsigned long long ull;
#define root tr[u]
#define ls tr[u<<1] 
#define rs tr[u<<1|1]
using namespace std;

const int N=100010;
int n;
struct Edge{
    double x,y1,y2;
    int v;
    bool operator<(const Edge &t) const{
        return x<t.x;
    }
}edge[2*N];

struct node{
    int l,r;
    int cnt;
    double len;
} tr[8*N];

vector<double> ys;

int find(double x){//注意数据类型!!!
    return lower_bound(ys.begin(),ys.end(),x)-ys.begin();
}

void pushup(int u){
    if(root.cnt) root.len=ys[root.r+1]-ys[root.l];
    else if(root.l !=root.r){
        root.len=rs.len+ls.len;
    }
    else root.len=0;
}

void build(int u,int l,int r){
    if(l==r) root={l,r,0,0};
    else{
        root={l,r,0,0};
        int mid=l+r>>1;
        build(u<<1,l,mid),build(u<<1|1,mid+1,r);
        // pushup(u);
    }
}

void modify(int u,int l,int r,int k){
    if(root.l>=l&&root.r<=r){
        root.cnt+=k;
        pushup(u);
    }
    else{
        int mid=root.r+root.l>>1;
        if(l<=mid) modify(u<<1,l,r,k);
        if(r>mid) modify(u<<1|1,l,r,k);
        pushup(u);
    }
}


int main(){
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);

    // cin>>n;
    int T=1;
    while(cin>>n,n){
        int idx=0;
        ys.clear();
        for(int i=0;i<n;i++){
            double x1,y1,x2,y2;
            cin>>x1>>y1>>x2>>y2;
            edge[idx++]={x1,y1,y2,1};
            edge[idx++]={x2,y1,y2,-1};
            ys.push_back(y1),ys.push_back(y2);
        }

        sort(ys.begin(),ys.end());
        ys.erase(unique(ys.begin(),ys.end()),ys.end());

        build(1,0,ys.size()-2);

        sort(edge,edge+n*2);

        double ans=0;
        for(int i=0;i<n*2;i++){
            if(i>0) ans+=tr[1].len*(edge[i].x-edge[i-1].x);
            modify(1,find(edge[i].y1),find(edge[i].y2)-1,edge[i].v);
        }

        cout<<"Test case #"<<T++<<endl;
        cout<<"Total explored area: "<<fixed<<setprecision(2)<<ans<<endl<<endl;
    }

    return 0;
}

luogu P1856 周长并

这题数据范围并不需要离散化

代码

#include <bits/stdc++.h>
#define endl "\n"
#define debug(x) cout << #x << ": -----> " << x << endl;
typedef long long ll;
// typedef unsigned long long ull;
#define root tr[u]
#define rs tr[u<<1|1]
#define ls tr[u<<1]
using namespace std;

const int N=10005;
int n;
struct edge{
    int l,r,len; //len:区间总长度(竖着)(通过作差得到答案)
    int add,numx; //add:懒标记(区间被完全覆盖的次数),numx目前在维护几个线段(横着)
    bool lt,rt; //是否包含边界,合并用,详细见下
} tr[4*N];
struct node{
    int x,y1,y2;
    int v;
    bool operator<(node &t) const{
        if(x==t.x) return v>t.v; //先去边再加边(放置出现在y方向上并列出现的样例)
        else return x<t.x;
    }
} seg[2*N];

void build(int u,int l,int r){
    if(l==r){
        tr[u]={l,r,0,0,0,0,0};
        return;
    }  
    else{
        tr[u]={l,r,0,0,0,0,0};
        int mid=l+r>>1;
        build(u<<1,l,mid),build(u<<1|1,mid+1,r);
        return;
    }
}

void pushup(int u){
    if(root.add){ //当前区间被覆盖了,他的长度就要统计,不论覆盖了几次,就算一次
        root.len=root.r-root.l+1; //维护信息的起源
        root.lt=root.rt=1;
        root.numx=1;
    }
    else if(root.l==root.r){ //维护区间信息不需要根节点
        root.lt=root.rt=0;
        root.len=0;
        root.numx=0;
    }
    else{
        root.len=ls.len+rs.len;
        root.lt=ls.lt;
        root.rt=rs.rt;
        root.numx=ls.numx+rs.numx-(ls.rt&rs.lt); //线段树维护的是连续的区间,如果同时覆盖了[1,2],[3,4],这种情况下,他们的(横边)是公用的,需要合并
    }
}

void modify(int u,int l,int r,int add){
    if(root.l>=l&&root.r<=r){
        root.add+=add;
        pushup(u);
    }
    else{
        int mid=root.r+root.l>>1; //debug了半天
        if(l<=mid) modify(u<<1,l,r,add);
        if(r>mid) modify(u<<1|1,l,r,add);
        pushup(u);
    }
}

int main(){
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin>>n;
    int idx=0;
    int _max=-0x3f3f3f3f,_min=0x3f3f3f3f;
    for(int i=0;i<n;i++){
        int x1,y1,x2,y2;cin>>x1>>y1>>x2>>y2;
        _min=min(_min,min(y1,y2)); _max=max(_max,max(y1,y2)); //线段树的l,r可以为负
        seg[++idx]={x1,y1,y2,1};
        seg[++idx]={x2,y1,y2,-1};
    }

    sort(seg+1,seg+idx+1);
    build(1,_min,_max-1); //用线段树维护区间时,长度是点数少一
    ll ans=0,last=0;

    for(int i=1;i<=idx;i++){
        modify(1,seg[i].y1,seg[i].y2-1,seg[i].v); //把每个边加入线段树,维护被覆盖的所有线段长
        ans+=abs(tr[1].len-last);
        ans+=(seg[i+1].x-seg[i].x)*2*tr[1].numx;
        last=tr[1].len;
    }

    cout<<ans;

    return 0;
}

/*
一些特殊样例
3
1 1 6 4
2 3 4 5
5 3 7 5 

2
0 0 4 4
0 4 4 8

*/
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wzx_1210

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值