扫描线与线段树结合题集总结

扫描线与线段树结合题集总结

扫描线其实是一种有效的处理多维问题的工具,可用于降维,二维问题中常用扫描线扫一维,另一维度通过数据结构维护,本题集较为基础,基本都是线段树与扫描线的结合。

扫描线需要注意的是排序的规则,不仅仅是坐标,还得考虑同坐标下的维护次序,重点重边,具体问题具体分析

1.poj1151

题意:

求n个矩形面积并

思路:

线段树维护扫描线最经典的操作,常把矩形入边赋值为正值,出边赋值为负边进行处理,端点分割线段,离散化端点,区间修改即可,维护每条线段是否完全被覆盖,以此来更新。

需要注意的是,由于只需要线段树根的答案,所以可以不用懒惰标记,直接pushUp即可,此处需要注意,线段的长度等于对应 X [ r + 1 ] − X [ l ] X[r+1]-X[l] X[r+1]X[l],还得注意l==r的时候要特判,此时如果继续向下的话就会越界再需要2倍空间

时间复杂度 O ( T n l o g n ) O(Tnlogn) O(Tnlogn)

void pushUp(int p,int l,int r){
    if(cnt[p])len[p]=X[r+1]-X[l];
    else if(l==r)len[p]=0;
    else len[p]=len[ls]+len[rs];
}
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define ls p<<1
#define rs p<<1|1
#define lson p<<1,l,mid
#define rson p<<1|1,mid+1,r
using namespace std;
typedef long long ll;
typedef double db;
const int maxn=100+5;
struct Line{
    db xL,xR,h;
    int flag;
    bool operator<(const Line&a){return h<a.h;}
}line[maxn<<1];
int add[maxn<<3],n;
db X[maxn<<1];//当前线段覆盖次数 长度 
db len[maxn<<3],ans=0,a,b,c,d;
void pushUp(int p,int l,int r){
    len[p]=add[p]?len[p]:(l==r?0:len[ls]+len[rs]);//到叶子的时候要特判回溯,不然越界,需要16倍空间
}
void update(int p,int l,int r,int L,int R,int val){
    if(L<=l&&r<=R){
        add[p]+=val;
        if(add[p])len[p]=X[r+1]-X[l];
        else len[p]=0;
        pushUp(p,l,r);
        return;
    }
    int mid=l+r>>1;
    if(L<=mid)update(lson,L,R,val);
    if(R>mid)update(rson,L,R,val);
    pushUp(p,l,r);
}
int main(){
    int T=0;
    while(~scanf("%d",&n)&&n){
        ans=0;
        memset(add,0,sizeof(add));
        memset(len,0,sizeof(len));
        for(int i=1;i<=n;++i){
            scanf("%lf%lf%lf%lf",&a,&b,&c,&d);
            line[(i<<1)-1]={a,c,b,-1};line[i<<1]={a,c,d,1};
            X[(i<<1)-1]=a;X[i<<1]=c;
        }
        sort(X+1,X+1+2*n);
        sort(line+1,line+1+2*n);
        int cnt=unique(X+1,X+1+2*n)-X-2;
        for(int i=1;i<=2*n;++i){
            int l1=lower_bound(X+1,X+1+cnt+1,line[i].xL)-X;
            int r1=lower_bound(X+1,X+1+cnt+1,line[i].xR)-X-1;
            ans+=1ll*len[1]*(line[i].h-line[i-1].h);
            update(1,1,cnt,l1,r1,line[i].flag);
        }
        printf("Test case #%d\n",++T);
        printf("Total explored area: %.2lf\n",ans);
        puts("");
    }
    return 0;
}
2.hdu 1828

题意:求矩形周长并

思路:横线用扫描线当前的长度-上一次遇到的长度,那竖线怎么维护呢?

容易想到是高度*竖线线段数量

我们可以在扫描的同时记录竖线上线段的出现次数,但是需要注意的是端点重合的时候就需要得减去,所以还得维护当前线段左右端点是否被覆盖,在左右儿子pushUp的时候减去

我这里傻逼了,事实上坐标即使是负的线段树也能维护

时间复杂度 O ( T n l o g n ) O(Tnlogn) O(Tnlogn)

#include<bits/stdc++.h>
#define ls p<<1
#define rs p<<1|1
#define lson p<<1,l,mid
#define rson p<<1|1,mid+1,r
using namespace std;
int n;
const int maxn=2e4+5;
struct Line{
    int xL,xR,flag,h;
    bool operator<(const Line&a){return h<a.h;}
}line[maxn];
int cnt[maxn<<2],num[maxn<<2],len[maxn<<2];//当前线段是否被覆盖,当前有多少竖线
bool Lf[maxn<<2],Rf[maxn<<2];//当前左右端点是否被覆盖
int a,b,c,d;
void pushUp(int p,int l,int r){
    if(cnt[p]){
        num[p]=2;
        Lf[p]=Rf[p]=1;
        len[p]=r-l+1;
    }else if(l==r){//不被覆盖时候防止越界
        num[p]=len[p]=Lf[p]=Rf[p]=0;
    }else{
        Lf[p]=Lf[ls];Rf[p]=Rf[rs];
        len[p]=len[ls]+len[rs];
        num[p]=num[ls]+num[rs];
        if(Rf[ls]&&Lf[rs])num[p]-=2;//左右都被覆盖就回去
    }
}
void update(int p,int l,int r,int L,int R,int val){
    if(L<=l&&r<=R){
        cnt[p]+=val;
        return pushUp(p,l,r);
    }
    int mid=l+r>>1;
    if(L<=mid)update(lson,L,R,val);
    if(R>mid)update(rson,L,R,val);
    pushUp(p,l,r);
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    while(cin>>n){
        int lmn=20001,rmx=1;
        for(int i=1;i<=n;++i){
            cin>>a>>b>>c>>d;
            a+=10001,c+=10001;
            lmn=min(lmn,a);rmx=max(rmx,c);
            line[(i<<1)-1]={a,c,1,b};line[i<<1]={a,c,-1,d};
        }
        sort(line+1,line+1+2*n);
        int last=0,ans=0;
        for(int i=1;i<=2*n;++i){
            if(line[i].xL<line[i].xR)
            update(1,1,rmx,line[i].xL,line[i].xR-1,line[i].flag);
            ans+=num[1]*(line[i+1].h-line[i].h);
            ans+=abs(len[1]-last);
            last=len[1];
        }
        cout<<ans<<"\n";
    }
    return 0;
}
3.poj2482

题意:你10000以内的星星的坐标和星星的亮度(即分别为x,y,c),要求用W*H的矩形去围住一个区域,使得这个区域内的星星的亮度最大,并且要求矩形边框上的星星不计入内。矩形可以平移,但不能旋转

思路:

由于边界不算,所以稍加处理后可以看成是每个点拓展为(x,y)~(x+w-1,y+h-1)的矩形,矩形带有权值,扫描线上下界改为权值,然后用线段树维护区间最大值,区间修改即可,值得学习的是把矩形点覆盖问题转化为矩形相互覆盖问题

值得注意的是,扫描线的排序除了坐标外有时还得注意边界,求周长和面积并不用管,但这里要管 ,因为当两边坐标相同的时候,有可能出现点重合满足的情况,所以先进入边,再进出边

时间复杂度 O ( T n l o g n ) O(Tnlogn) O(Tnlogn)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define lson p<<1,l,mid
#define rson p<<1|1,mid+1,r
#define ls p<<1
#define rs p<<1|1
using namespace std;
typedef long long ll;
int n,w,h;
const int maxn=1e4+5;
struct Line{
    ll xL,xR,h;
    int c;
    bool operator<(const Line&a)const{
        if(h==a.h)return c>a.c;
        return h<a.h;
    }
}line[maxn<<1];
int mx[maxn<<3],add[maxn<<3],c;
ll X[maxn<<1],x,y;
void pushUp(int p){
    mx[p]=max(mx[ls],mx[rs]);
}
void pushDown(int p,int l,int r){
    if(l==r){ add[p]=0;return;}
    if(add[p]){
        add[ls]+=add[p];
        add[rs]+=add[p];
        mx[ls]+=add[p];
        mx[rs]+=add[p];
        add[p]=0;
    }
}
void update(int p,int l,int r,int L,int R,int val){
    if(L<=l&&r<=R){
        mx[p]+=val;
        add[p]+=val;
        return;
    }
    int mid=l+r>>1;
    pushDown(p,l,r);
    if(L<=mid)update(lson,L,R,val);
    if(R>mid)update(rson,L,R,val);
    pushUp(p);
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    while(cin>>n>>w>>h){
        for(int i=1;i<=8*n;++i)add[i]=mx[i]=0;
        for(int i=1;i<=n;++i){
            cin>>x>>y>>c;
            line[(i<<1)-1]={x,x+w-1,y,c};
            line[i<<1]={x,x+w-1,y+h-1,-c};
            X[(i<<1)-1]=x;
            X[(i<<1)]=x+w-1;
        }
        sort(X+1,X+1+2*n);
        sort(line+1,line+1+2*n);
        int cnt=unique(X+1,X+1+2*n)-(X+1);
        int ans=0;
        for(int i=1;i<=2*n;++i){
            int l1=lower_bound(X+1,X+1+cnt,line[i].xL)-X;
            int r1=lower_bound(X+1,X+1+cnt,line[i].xR)-X;
            update(1,1,cnt,l1,r1,line[i].c);
            ans=max(ans,mx[1]);
        }
        cout<<ans<<"\n";
    }
    return 0;
}

4.UVA11983

题意:求矩形至少覆盖k次以上面积和

思路:

这题非常重要,模板要留着

其实就是矩形面积的拓展,求矩形面积其实相当于是求覆盖1次以上的面积和而已,像这种限定次数的,我们可以在线段树上维护一个k大的数组,关键在于pushUp的更新,

首先当前的所有的长度必定完全取决于左右儿子,所以每次更新后本身都得清空,初态的时候覆盖0次的记得赋值,不然第一次上传会出错,cnt[p]此时应该表示当前区间最少覆盖为cnt[p]次,更大x次的应由左右儿子对应的x-cnt[p]次数上传,

此时的 l e n [ p ] [ i ] len[p][i] len[p][i]除了最大的 l e n [ p ] [ k ] len[p][k] len[p][k]记录的是至少,其他应该都为恰好i次

时间复杂度 O ( T k n l o g n ) O(Tknlogn) O(Tknlogn)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OSw7mEbX-1612451780791)(C:\Users\98753\AppData\Roaming\Typora\typora-user-images\image-20210204221810579.png)]

//矩形k次以上面积并 原题是覆盖整数点右上坐标+1转化
#include<bits/stdc++.h>
#define lson p<<1,l,mid
#define rson p<<1|1,mid+1,r
#define ls p<<1
#define rs p<<1|1
using namespace std;
typedef long long ll;
const int maxn=3e4+5;
struct Line{
    int xL,xR,flag,h;
    bool operator<(const Line&a){return h<a.h;}
}line[maxn<<1];
int t,n,k,X[maxn<<1];
struct SegmentTree{
    int len[maxn<<3][11],cnt[maxn<<3];//被覆盖i次以上的长度以及当前区间被覆盖次数
    void pushUp(int p,int l,int r){
        for(int i=0;i<=k;++i)len[p][i]=0;//上一次更新后得清空
        if(cnt[p]>=k){//当前覆盖次数>=k
            len[p][k]=X[r+1]-X[l];
        }else if(l==r){//叶子结点只更新对应次数
            len[p][cnt[p]]=X[r+1]-X[l];
        }else{//覆盖不满的话,得把子结点标记影响上传
            for(int i=0;i<=k;++i)
                len[p][min(k,cnt[p]+i)]+=len[ls][i]+len[rs][i];
        }
    }
    void build(int p,int l,int r){
        cnt[p]=0;
        for(int i=1;i<=k;++i)len[p][i]=0;
        len[p][0]=X[r+1]-X[l];//初态的时候得赋值
        if(l==r)return;
        int mid=l+r>>1;
        build(lson);
        build(rson);
    }
    void update(int p,int l,int r,int L,int R,int val){
        if(L<=l&&r<=R){
            cnt[p]+=val;
            return pushUp(p,l,r);
        }
        int mid=l+r>>1;
        if(L<=mid)update(lson,L,R,val);
        if(R>mid)update(rson,L,R,val);
        pushUp(p,l,r);
    }
}tr;
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin>>t;
    int T=0;
    while(t--){
        cin>>n>>k;
        int a,b,c,d;
        line[0].h=0;
        for(int i=1;i<=n;++i){
            cin>>a>>b>>c>>d;
            c++;d++;
            line[(i<<1)-1]={a,c,1,b};line[i<<1]={a,c,-1,d};
            X[(i<<1)-1]=a;X[i<<1]=c;
        }
        sort(line+1,line+1+2*n);
        sort(X+1,X+1+2*n);
        int m=unique(X+1,X+1+2*n)-(X+2);
        tr.build(1,1,m);
        ll ans=0;
        for(int i=1;i<=2*n;++i){
            ans+=1ll*tr.len[1][k]*(line[i].h-line[i-1].h);
            int l1=lower_bound(X+1,X+2+m,line[i].xL)-X;
            int r1=lower_bound(X+1,X+2+m,line[i].xR)-X-1;
            tr.update(1,1,m,l1,r1,line[i].flag);
        }
        cout<<"Case "<<++T<<": "<<ans<<"\n";
    }
    return 0;
}

5.hdu 3642

题意:

给你长方体,求至少3个长方体体积交的和

思路:

注意到z的坐标很小,直接分层枚举,每一层都对内部的长方体做扫描线,套个矩形面积k次并板子就秒了

复杂度 O ( T ∗ 500 ∗ n l o g n ) O(T*500*nlogn) O(T500nlogn)

#include<bits/stdc++.h>
#define lson p<<1,l,mid
#define rson p<<1|1,mid+1,r
#define ls p<<1
#define rs p<<1|1
using namespace std;
typedef long long ll;
const int maxn=1005;
struct Line{
    int xL,xR,flag,h;
    bool operator<(const Line&a){return h<a.h;}
}line[maxn<<1];
int X[maxn<<1],Z[maxn<<1],t,n,T=0;
struct SegmentTree{
    int cnt[maxn<<3],len[maxn<<3][4];
    void build(int p,int l,int r){
        cnt[p]=0;
        for(int i=1;i<=3;++i)len[p][i]=0;
        len[p][0]=X[r+1]-X[l];
        if(l==r)return;
        int mid=l+r>>1;
        build(lson);
        build(rson);
    }
    void pushUp(int p,int l,int r){
        for(int i=0;i<=3;++i)len[p][i]=0;
        if(cnt[p]>=3)len[p][3]=X[r+1]-X[l];
        else if(l==r)len[p][cnt[p]]=X[r+1]-X[l];
        else{
            for(int i=0;i<=3;++i)
                len[p][min(3,i+cnt[p])]+=len[ls][i]+len[rs][i];
        }
    }
    void update(int p,int l,int r,int L,int R,int val){
        if(L<=l&&r<=R){
            cnt[p]+=val;
            return pushUp(p,l,r);
        }
        int mid=l+r>>1;
        if(L<=mid)update(lson,L,R,val);
        if(R>mid)update(rson,L,R,val);
        pushUp(p,l,r);
    }
}tr;
struct Node{
    int X1,Y1,Z1,X2,Y2,Z2;
}node[maxn];
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin>>t;
    while(t--){
        cin>>n;
        for(int i=1;i<=n;++i){
            cin>>node[i].X1>>node[i].Y1>>node[i].Z1>>node[i].X2>>node[i].Y2>>node[i].Z2;
            Z[(i<<1)-1]=node[i].Z1,Z[(i<<1)]=node[i].Z2;
        }
        if(n<=2){
            cout<<"Case "<<++T<<": 0\n";continue;
        }
        sort(Z+1,Z+1+2*n);
        int cntz=unique(Z+1,Z+1+2*n)-(Z+1),numline=0;
        ll ans=0;
        for(int i=2;i<=cntz;++i){
            int H=Z[i]-Z[i-1],cntX=0;
            numline=0;
            for(int j=1;j<=n;++j){
                if(node[j].Z2<=Z[i-1]||node[j].Z1>=Z[i])continue;
                line[++numline]={node[j].X1,node[j].X2,1,node[j].Y1};
                line[++numline]={node[j].X1,node[j].X2,-1,node[j].Y2};
                X[++cntX]=node[j].X1;X[++cntX]=node[j].X2;
            }
            sort(line+1,line+1+numline);
            sort(X+1,X+1+cntX);
            int m=unique(X+1,X+1+cntX)-(X+2);
            tr.build(1,1,m);
            for(int j=1;j<=numline;++j){
                ans+=1ll*H*(line[j].h-line[j-1].h)*tr.len[1][3];
                int l1=lower_bound(X+1,X+2+m,line[j].xL)-X;
                int r1=lower_bound(X+1,X+2+m,line[j].xR)-(X+1);
                tr.update(1,1,m,l1,r1,line[j].flag);
            }
        }
        cout<<"Case "<<++T<<": "<<ans<<"\n";
    }
    return 0;
}
6.hdu3255

题意:有N块农田,每块农田中种一种作物,每种作物都有一个价格,当在同一区域内种植了两种不同的作物时,作物价格大的生存下来,作物价格小的死亡。求最后的所有作物的能买的总钱数。

思路:

一开始一直不知道如何处理这个最值变化,在线段树上区间更新维护也很麻烦,因为除了历史最值维护,还得删去之前的某个数作为最值的可能,看了题解恍然大悟,与上题一样,由于种类很少,二维转三维相当于立方体的高度,一下子就解决了最值覆盖问题,直接矩形体积交即可

O ( 9 T n l o g n ) O(9Tnlogn) O(9Tnlogn)

#include<bits/stdc++.h>
#define lson p<<1,l,mid
#define rson p<<1|1,mid+1,r
#define ls p<<1
#define rs p<<1|1
using namespace std;
typedef long long ll;
const int maxn=3e4+5;
struct Line{
    int xL,xR,flag,h;
    bool operator<(const Line&a)const{return h<a.h;}
}line[maxn<<1];
int n,m,t,val[5],T=0,X[maxn<<1],hi[5];
struct RC{
    int X1,Y1,X2,Y2,Z;
}rec[maxn];
struct SegmentTree{
    int cnt[maxn<<3],len[maxn<<3];
    void build(int p,int l,int r){
        cnt[p]=len[p]=0;
        if(l==r)return;
        int mid=l+r>>1;
        build(lson);
        build(rson);
    }
    void pushUp(int p,int l,int r){
        if(cnt[p])len[p]=X[r+1]-X[l];
        else if(l==r)len[p]=0;
        else len[p]=len[ls]+len[rs];
    }
    void update(int p,int l,int r,int L,int R,int val){
        if(L<=l&&r<=R){
            cnt[p]+=val;
            return pushUp(p,l,r);
        }
        int mid=l+r>>1;
        if(L<=mid)update(lson,L,R,val);
        if(R>mid)update(rson,L,R,val);
        pushUp(p,l,r);
    }
}tr;
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin>>t;
    while(t--){
        cin>>n>>m;
        for(int i=1;i<=m;++i)cin>>val[i],hi[i]=val[i];
        val[++m]=0;
        for(int i=1;i<=n;++i){
            cin>>rec[i].X1>>rec[i].Y1>>rec[i].X2>>rec[i].Y2>>rec[i].Z;
        }
        sort(val+1,val+1+m);
        int cntz=unique(val+1,val+1+m)-(val+1);
        ll ans=0;
        for(int i=2;i<=cntz;++i){
            int h=val[i]-val[i-1],cntx=0,cntline=0;
            for(int j=1;j<=n;++j){
                if(hi[rec[j].Z]<=val[i-1]||0>=val[i])continue;
                X[++cntx]=rec[j].X1,X[++cntx]=rec[j].X2;
                line[++cntline]={rec[j].X1,rec[j].X2,1,rec[j].Y1};
                line[++cntline]={rec[j].X1,rec[j].X2,-1,rec[j].Y2};
            }
            sort(X+1,X+1+cntx);
            sort(line+1,line+1+cntline);
            int numx=unique(X+1,X+1+cntx)-(X+2);
            tr.build(1,1,numx);
            for(int j=1;j<=cntline;++j){
                ans+=1ll*tr.len[1]*h*(line[j].h-line[j-1].h);
                int l1=lower_bound(X+1,X+2+numx,line[j].xL)-X;
                int r1=lower_bound(X+1,X+2+numx,line[j].xR)-(X+1);
                tr.update(1,1,numx,l1,r1,line[j].flag);
            }
        }
        cout<<"Case "<<++T<<": "<<ans<<"\n";
    }
    return 0;
}

7.poj2464

题意:在平面直角坐标系中给你N个点,stan和ollie玩一个游戏,首先stan在竖直方向上画一条直线,该直线必须要过其中的某个点,然后ollie在水平方向上画一条直线,该直线的要求是要经过一个stan之前画过的点。这时候平面就被分割成了四块,两个人这时候会有一个得分,stan的得分是平面上第1、3象限内的点的个数,ollie的得分是平面上第2、4象限内的点的个数,在统计的时候在所画线上的点都不计算在内。求最终stan使得自己的最差得分最高,并且输出此时ollie的得分。

思路:

一眼二维树状数组,但是存不下,考虑降维,发现可以通过预处理出某点正上下左右方以及上面的全部的点数,这样暴力枚举竖线,横线,就可以用树状数组维护y轴算出来了

需要注意两点:1.没有的时候O的方案是要输出0的!!!,坑死

2.第二个去重

细节挺多的,有点恶心

O ( T n l o g n ) O(Tnlogn) O(Tnlogn)

8.hdu 4419

思路:

直接利用二进制124,即可表示所有,注意与一般的矩形面积并不同的在于,不能重复+=覆盖次数,所以用二进制或运算维护, c n t [ p ] [ i ] cnt[p][i] cnt[p][i]表示第i种颜色是否出现,具体看pushUp函数

#include<bits/stdc++.h>
#define lson p<<1,l,mid
#define rson p<<1|1,mid+1,r
#define ls p<<1
#define rs p<<1|1
using namespace std;
typedef long long ll;
const int maxn=1e4+5;
struct Line{
    int xL,xR,flag,h;
    bool operator<(const Line&a){
        return h<a.h;
    }
}line[maxn<<1];
unordered_map<char,int>mp;
int t,n,a,b,c,d,X[maxn<<1],id[8];
ll ans[8];
char s;
struct SegmentTree{
    int cnt[maxn<<3][8],len[maxn<<3][8];
    void build(int p,int l,int r){
        for(int i=1;i<=7;++i)len[p][i]=0,cnt[p][i]=0;
        len[p][0]=X[r+1]-X[l];
        if(l==r)return;
        int mid=l+r>>1;
        build(lson);
        build(rson);
    }
    void pushUp(int p,int l,int r){
        int tmp=0;
        if(cnt[p][1])tmp|=1;
        if(cnt[p][2])tmp|=2;
        if(cnt[p][4])tmp|=4;
        for(int i=0;i<=7;++i)len[p][i]=0;
        if(tmp){ 
            len[p][tmp]=X[r+1]-X[l];
            for(int i=1;i<=7;++i){ 
                if(l!=r&&tmp!=(tmp|i)){//叶子不能再递归 
                    len[p][tmp|i]+=len[ls][i]+len[rs][i];
                    len[p][tmp]-=len[ls][i]+len[rs][i];
                }
            }
        }else 
            if(l!=r)
                for(int i=1;i<=7;++i)len[p][i]=len[ls][i]+len[rs][i];
    }
    void update(int p,int l,int r,int L,int R,int val){
        if(L<=l&&r<=R){
            if(val>0)cnt[p][val]++;
            else cnt[p][-val]--;
            return pushUp(p,l,r);
        }
        int mid=l+r>>1;
        if(L<=mid)update(lson,L,R,val);
        if(R>mid)update(rson,L,R,val);
        pushUp(p,l,r);
    }
}tr;
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    mp['R']=1;mp['G']=2;mp['B']=4;
    id[1]=1;id[2]=2;id[3]=4;id[4]=3;id[5]=5;id[6]=6;id[7]=7;
    cin>>t;
    int T=0;
    while(t--){
        memset(ans,0,sizeof(ans));
        cin>>n;
        for(int i=1;i<=n;++i){
            cin>>s>>a>>b>>c>>d;
            line[(i<<1)-1]={a,c,mp[s],b};line[(i<<1)]={a,c,-mp[s],d};
            X[(i<<1)-1]=a;X[i<<1]=c;
        }
        sort(X+1,X+1+2*n);
        sort(line+1,line+1+2*n);
        int m=unique(X+1,X+1+2*n)-(X+2);
        tr.build(1,1,m);
        for(int i=1;i<=2*n;++i){
            for(int j=1;j<=7;++j)ans[j]+=1ll*(line[i].h-line[i-1].h)*tr.len[1][id[j]];
            int l=lower_bound(X+1,X+2+m,line[i].xL)-X;
            int r=lower_bound(X+1,X+2+m,line[i].xR)-X-1;
            tr.update(1,1,m,l,r,line[i].flag);
        }
        cout<<"Case "<<++T<<":\n";
        for(int i=1;i<=7;++i)
            cout<<ans[i]<<"\n";
    }
    return 0;
}

9.gym101982F

题意:

求矩形奇数次面积覆盖

思路:

看到奇偶用线段树维护区间反转标记,奇数就是反转吗,有的变没的,没的变有,所以奇数次的长度其实就是相当于当前区间的长度减去当前覆盖的长度

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define ls p<<1
#define rs p<<1|1
#define lson p<<1,l,mid
#define rson p<<1|1,mid+1,r
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
struct Line{
    int xL,xR,h;
    bool operator<(const Line&a){return h<a.h;}
}line[maxn<<1];
int X[maxn<<1],n,a,b,c,d;
struct SegmentTree{ 
    int cnt[maxn<<3],len[maxn<<3];
    void build(int p,int l,int r){ 
        cnt[p]=len[p]=0;
        if(l==r)return;
        int mid=l+r>>1;
        build(lson);
        build(rson);
    }
    void pushDown(int p,int l,int r){ 
        if(l==r)return;
        if(cnt[p]){ 
            cnt[ls]^=1;
            cnt[rs]^=1;
            int mid=l+r>>1;
            len[ls]=X[mid+1]-X[l]-len[ls];
            len[rs]=X[r+1]-X[mid+1]-len[rs];
            cnt[p]=0;
        }
    }
    void update(int p,int l,int r,int L,int R){
        if(L<=l&&r<=R){
            cnt[p]^=1;
            len[p]=X[r+1]-X[l]-len[p];
            return;
        }
        int mid=l+r>>1;
        pushDown(p,l,r);
        if(L<=mid)update(lson,L,R);
        if(R>mid)update(rson,L,R);
        len[p]=len[ls]+len[rs];
    }
}tr;
ll ans=0;
int main(){
        ios::sync_with_stdio(false);
        cin.tie(0);
        ans=0;
        cin>>n;
        for(int i=1;i<=n;++i){
            cin>>a>>b>>c>>d;
            line[(i<<1)-1]={a,c,b};line[i<<1]={a,c,d};
            X[(i<<1)-1]=a;X[i<<1]=c;
        }
        sort(X+1,X+1+2*n);
        sort(line+1,line+1+2*n);
        int cnt=unique(X+1,X+1+2*n)-X-2;
        for(int i=1;i<=2*n;++i){
            int l1=lower_bound(X+1,X+1+cnt+1,line[i].xL)-X;
            int r1=lower_bound(X+1,X+1+cnt+1,line[i].xR)-X-1;
            ans+=1ll*tr.len[1]*(line[i].h-line[i-1].h);
            tr.update(1,1,cnt,l1,r1);
        }
        cout<<ans<<"\n";
        return 0;
}

10.gym102576E

题意:平面上有 n n n个两两相离的圆,给定它们的坐标和半径。
q q q次询问,每次给定两个点 p p p, q q q,给出它们的坐标以及在 y y y轴的可移动范围 [ y m i n , y m a x ] [ y m i n , y m a x ] [ymin,ymax][ymin,ymax] [ymin,ymax][ymin,ymax],问两点能否互相到达。

思路:

本质就是转化为问两点之间是否存在圆满足 y − r < = y m n y-r<=ymn yr<=ymn y + r > = y m x 且 m i n ( p x , q x ) < = x < = m a x ( p x , q x ) y+r>=ymx 且 min(px,qx)<=x<=max(px,qx) y+r>=ymxmin(px,qx)<=x<=max(px,qx)

然后我就歇逼不会了,这题看了题解才会,本质就是扫描线,每扫描到一个 y − r y-r yr就把 y + r y+r y+r在对应的x处维护最大值,然后遇到ymn就直接在[min(px,qx),max(px,qx)]查询区间最大值,离散化和动态开点都是可以的,当然前者常数小点

需要注意的是,排序的时候假如圆和ymn重合,必须先加入圆,因为这也是合法的

#include<bits/stdc++.h>
#define lson p<<1,l,mid
#define rson p<<1|1,mid+1,r
#define ls p<<1
#define rs p<<1|1
using namespace std;
int n,q;
const int maxn=1e6+5;
const int INF=1e9;
struct Line{ 
    int ymn,ymx,id,x1,x2;
    bool operator<(const Line&a)const{ 
        if(ymn==a.ymn)return id<a.id;//等y的要先加入圆
        return ymn<a.ymn;
    }
}line[maxn<<1];
int cnt=0,px,py,qx,qy,ymn,ymx,X[maxn],cntx=0,m;
struct SegmentTree{ 
    int mx[maxn<<2];
    void pushUp(int p){ 
        mx[p]=max(mx[ls],mx[rs]);
    }
    void update(int p,int l,int r,int x,int val){ 
        if(l==r){ 
            mx[p]=max(mx[p],val);
            return;
        }
        int mid=l+r>>1;
        if(x<=mid)update(lson,x,val);
        else update(rson,x,val);
        pushUp(p);
    }
    int query(int p,int l,int r,int L,int R){ 
        if(L<=l&&r<=R)return mx[p];
        int mid=l+r>>1;
        int ans1=-INF-1,ans2=-INF-1;
        if(L<=mid)ans1=query(lson,L,R);
        if(R>mid)ans2=query(rson,L,R);
        return max(ans1,ans2);
    }
}tr;
bool ans[maxn];
int ask(int x){ 
    return lower_bound(X+1,X+1+m,x)-X;
}
int main(){ 
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin>>n>>q;
    int x,y,r;
    for(int i=1;i<=n;++i){ 
        cin>>x>>y>>r;
        line[++cnt]={y-r,y+r,0,x,0};
        X[++cntx]=x;
    }
    for(int i=1;i<=q;++i){ 
        cin>>px>>py>>qx>>qy>>ymn>>ymx;
        line[++cnt]={ymn,ymx,i,px,qx};
    }
    sort(line+1,line+1+cnt);
    sort(X+1,X+1+cntx);
    m=unique(X+1,X+1+cntx)-(X+1);
    for(int i=1;i<=4*m;++i)tr.mx[i]=-INF-1;
    for(int i=1;i<=cnt;++i){ 
        if(!line[i].id)tr.update(1,1,m,ask(line[i].x1),line[i].ymx);
        else{ 
            int l1=ask(min(line[i].x2,line[i].x1)),r1=upper_bound(X+1,X+1+m,max(line[i].x1,line[i].x2))-X-1;
            int num;
            if(r1<l1)num=-INF-5;
            else num=tr.query(1,1,m,l1,r1);
            ans[line[i].id]=(num>=line[i].ymx)?1:0;
        }
    }
    for(int i=1;i<=q;++i){ 
        puts(ans[i]?"NO":"YES");
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值