• 【线段树】扫描线学习笔记


    之后有一回在luogu做了一道同样求矩形周长的题,用了下面的模板,但是有组数据过不了,需要做如下修改:

    重载运算符函数修改成:  bool operator <(const P&p)const{return h==p.h?ju<p.ju:h<p.h;}


    之前并不能理解扫描线这种东西,真的以为是条线,还在想这东西到底要怎么实现?

    结果理解后,好像还真是条线。

    好吧。其实是以一个坐标轴的点确定的直线+长度,直线和长度便确定了线段,也有了值。

    扫描线应该说是靠顺序查询轴线点来实现的。

    看了一个大佬的博客,受益匪浅。我是看了这个博客才明白扫描线这东西的做法。

    总的来说,是先确定一个轴线为遍历方向,以该方向从小到大排序点,

    然后扫描线就是:以确定的轴线方向(设为第一坐标轴)为扫描方向,按从小到大的顺序(通常都是从小到大),逐个遍历目标点的坐标,以该点的另一个坐标轴的方向(设为第二坐标轴)确定另一条直线,然后在遍历的过程中,以某种方法(比如线段树),尝试确定当前点的第二坐标轴方向直线的有效长度,

      有效长度指的是:当前直线到下一条直线所围面积的底边(即这根直线上的边)的长度。

    然后以这个当前有效长度,来实现我们的目的。好比如,计算面积,就是逐个遍历,用当前有效长度,乘以与下一条直线间的距离(即第一坐标轴相邻两点的距离)。

    我看的博客的代码,当前有效长度是以sum[1]数组记录。用线段树更新。

    另外,有将第一坐标轴的点离散化。

    然后根据那个博客,我改了里边的代码,做了我扫描线的第一题,洛谷P1904

    给定以x轴为下底边的矩形的信息,求矩形轮廓外轮廓。

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=5e3+50;
    int mark[maxn<<2];//记录某个区间的下底边个数
    int sum[maxn<<2];//记录某个区间的下底边总长度
    int a[maxn<<1];//对h进行离散化
    //以纵坐标作为线段(区间),对纵坐标线段进行扫描 
    struct P{
        int x,h,ju;//以h为扫描线平行边,x为扫描方向,ju=-1为左 ju=1为右
        bool operator <(const P&p)const{return x<p.x;}
        P(){}
        P(int xx,int hh,int juu){x=xx;h=hh;ju=juu;}
    }s[maxn<<1];
    void upfather(int k,int l,int r){
        if(mark[k])sum[k]=a[r+1]-a[l];//
        else if(l==r)sum[k]=0;
        else sum[k]=sum[k<<1]+sum[k<<1|1];
    }//n k
    void update(int L,int R,int ju,int k,int l,int r){
        if(L<=l&&r<=R){
            mark[k]+=ju;
            upfather(k,l,r);
            return;
        }
        int mid=(l+r)>>1;
        if(L<=mid)update(L,R,ju,k<<1,l,mid);
        if(R>mid)update(L,R,ju,k<<1|1,mid+1,r);
        upfather(k,l,r);
    }
    int search(int key,int *x,int k){
        int l=0,r=k-1;
        while(l<=r){
            int mid=(l+r)>>1;
            if(x[mid]==key)return mid;
            if(x[mid]>key)r=mid-1;
            else l=mid+1; 
        }
        return -1;
    }
    struct PP{
        int x,y;
        PP(){}
        PP(int xx,int yy){x=xx;y=yy;}
    }ans[maxn<<2];
    int solve(int tot,int up)//tot是边数 up是点数 
    {
        int upp=0,tans=0;
        for(int i=0;i<tot;i++){
            int R=search(s[i].h,a,up)-1;
            update(0,R,s[i].ju,1,0,up-1);
            if(sum[1]==tans)continue;
            if(ans[upp-1].x==s[i].x&&ans[upp-1].y==tans)upp--;
            else ans[upp++]=PP(s[i].x,tans);
            if(ans[upp-1].x==s[i].x&&ans[upp-1].y==sum[1])upp--;
            else ans[upp++]=PP(s[i].x,sum[1]);
            tans=sum[1];
        }
        return upp;
    }
    int main()
    {
        int n,tot=0,l,h,r,up=0;
        a[up++]=0;
    //    scanf("%d",&n);
        while(~scanf("%d%d%d",&l,&h,&r))
    //    while(tot<n*2)
        {
    //        scanf("%d%d%d",&l,&h,&r);
            a[up++]=h;
            s[tot++]=P(l,h,-1);
            s[tot++]=P(r,h,1);
        }
        sort(a,a+up);
        sort(s,s+tot);
        up=unique(a,a+up)-a;
        int upp=solve(tot,up);
        for(int i=0;i<upp;i++)
        {
            if(i&1)printf("%d ",ans[i].y);
            else printf("%d ",ans[i].x);
    //         printf("%d% d
    ",ans[i].x,ans[i].y);
        }
    }

    2019-09-04

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    扫描线第二题 HDU1542

    给定矩形信息,求总面积。

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=1e2+50;
    int mark[maxn<<2];//记录某个区间的下底边个数
    double sum[maxn<<2];//记录某个区间的下底边总长度
    double a[maxn<<1];//对x进行离散化
    //
    struct P{
        double l,r,h;
        int ju;
        bool operator <(const P&p)const{return h<p.h;}
        P(){}
        P(double ll,double rr,double hh,int juu)
        {l=ll;r=rr;h=hh;ju=juu;}
    }s[maxn<<1];
    
    void upfather(int k,int l,int r){
        if(mark[k])sum[k]=a[r+1]-a[l];
        else if(l==r)sum[k]=0;
        else sum[k]=sum[k<<1]+sum[k<<1|1];
    }
    void update(int L,int R,int ju,int k,int l,int r){
        if(L<=l&&r<=R){
            mark[k]+=ju;
            upfather(k,l,r);
            return;
        }
        int mid=(l+r)>>1;
        if(L<=mid)update(L,R,ju,k<<1,l,mid);
        if(R>mid)update(L,R,ju,k<<1|1,mid+1,r);
        upfather(k,l,r);
    }
    int search(double key,double *x,int k){
        int l=0,r=k-1;
        while(l<=r){
            int mid=(l+r)>>1;
            if(x[mid]==key)return mid;
            if(x[mid]>key)r=mid-1;
            else l=mid+1; 
        }
        return -1;
    }
    
    double solve(int tot,int up)//tot是边数 up是点数 
    {
        double ans=0;
        for(int i=0;i<tot;i++){
            int L=search(s[i].l,a,up);
            int R=search(s[i].r,a,up)-1;
            update(L,R,s[i].ju,1,0,up-1);
            ans+=sum[1]*(s[i+1].h-s[i].h);
        }
        return ans;
    }
    
    int main()
    {
        int n,cas=0;
        while(scanf("%d",&n)!=EOF&&n)
        {
            cas++;
            memset(a,0,sizeof(a));
            memset(s,0,sizeof(s));
            int i,tot=0;
            double x1,y1,x2,y2;
            for(i=0;i<n;i++)
            {
                scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
                a[tot]=x1;
                s[tot++]=P(x1,x2,y1,-1);
                a[tot]=x2;
                s[tot++]=P(x1,x2,y2,1);
            }
            sort(a,a+tot);
            sort(s,s+tot);
            int up=unique(a,a+tot)-a;
            printf("Test case #%d
    ",cas);
            printf("Total explored area: %.2lf
    
    ",solve(tot,up));
        }
    }

    2019-09-05

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    第三题,POJ1177

    给定矩形信息,求周长。

    (好的做法应该是用线段树的连通性维护当前扫描线有几段,然后另一方向的长度直接等于2*num*h,然而我试了,不会做,只能老老实实再在另一方向扫描一遍。)

    #include<bits/stdc++.h>
    #define debug printf("!");
    using namespace std;
    typedef long long ll;
    const int maxn=1e4+50;
    const int inf=0x3f3f3f3f;
    
    
    int mark[maxn<<2];//记录某个区间的下底边个数
    ll sum[maxn<<2];//记录某个区间的下底边总长度
    int a1[maxn<<1],a2[maxn<<1];//对x进行离散化
    //
    struct P{
        int l,r,h;
        int ju;
        bool operator <(const P&p)const{return h==p.h?ju<p.ju:h<p.h;}//
        /*
        排序需要ju作为第二关键字
        测试样例
        2
        0 0 4 4
        0 4 4 8
        
        使得上面图形的底边 和 下面图形的顶边 在重边时避免多算 
        
        */
    }s1[maxn<<1],s2[maxn<<1];
    void upfather(int k,int l,int r,int a[]){
        if(mark[k])sum[k]=a[r+1]-a[l];
        else if(l==r)sum[k]=0;
        else sum[k]=sum[k<<1]+sum[k<<1|1];
    }
    void update(int L,int R,int ju,int k,int l,int r,int a[]){
        if(L<=l&&r<=R){
            mark[k]+=ju;
            upfather(k,l,r,a);
            return;
        }
        int mid=(l+r)>>1;
        if(L<=mid)update(L,R,ju,k<<1,l,mid,a);
        if(R>mid)update(L,R,ju,k<<1|1,mid+1,r,a);
        upfather(k,l,r,a);
    }
    int search(int key,int *x,int k){
        int l=0,r=k-1;
        while(l<=r){
            int mid=(l+r)>>1;
            if(x[mid]==key)return mid;
            if(x[mid]>key)r=mid-1;
            else l=mid+1; 
        }
        return -1;
    }
    
    ll solve(int tot,int up,P s[],int a[])//tot是边数 up是点数 
    {
        ll ans=0,tans=0;
        for(int i=0;i<tot;i++){
            int L=search(s[i].l,a,up);
            int R=search(s[i].r,a,up)-1;
            update(L,R,s[i].ju,1,0,up-1,a);
            if(sum[1]-tans>=0)ans+=sum[1]-tans;
            else ans-=sum[1]-tans;
            tans=sum[1];
        }
        return ans;
    }
    int main()
    {
        int i,n,tot=0,up;
        int x1,y1,x2,y2;
        ll ans=0;
        scanf("%d",&n);
        for(i=0;i<n;i++)
        {
            scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
            a1[tot]=x1;
            a2[tot]=y1;
            s1[tot]=P{x1,x2,y1,-1};
            s2[tot++]=P{y1,y2,x1,-1};
            a1[tot]=x2;
            a2[tot]=y2;
            s1[tot]=P{x1,x2,y2,1};
            s2[tot++]=P{y1,y2,x2,1};
        }
        sort(a1,a1+tot);sort(a2,a2+tot);
        sort(s1,s1+tot);sort(s2,s2+tot);
        up=unique(a1,a1+tot)-a1;
        ans+=solve(tot,up,s1,a1);
        memset(sum,0,sizeof(sum));
        memset(mark,0,sizeof(mark));
        up=unique(a2,a2+tot)-a2;
        ans+=solve(tot,up,s2,a2);
        printf("%lld
    ",ans);
    }

    2019-09-05 20:08:06

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    第四题,洛谷P382

    跟第一题几乎一样,改点输入输出和一个小细节就可以了:D。

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=1e5+50;
    int mark[maxn<<2];//记录某个区间的下底边个数
    int sum[maxn<<2];//记录某个区间的下底边总长度
    int a[maxn<<1];//对h进行离散化
    //以纵坐标作为线段(区间),对纵坐标线段进行扫描 
    struct P{
        int x,h,ju;//以h为扫描线平行边,x为扫描方向,ju=-1为左 ju=1为右
        bool operator <(const P&p)const{return x<p.x;}
        P(){}
        P(int xx,int hh,int juu){x=xx;h=hh;ju=juu;}
    }s[maxn<<1];
    void upfather(int k,int l,int r){
        if(mark[k])sum[k]=a[r+1]-a[l];//
        else if(l==r)sum[k]=0;
        else sum[k]=sum[k<<1]+sum[k<<1|1];
    }//n k
    void update(int L,int R,int ju,int k,int l,int r){
        if(L<=l&&r<=R){
            mark[k]+=ju;
            upfather(k,l,r);
            return;
        }
        int mid=(l+r)>>1;
        if(L<=mid)update(L,R,ju,k<<1,l,mid);
        if(R>mid)update(L,R,ju,k<<1|1,mid+1,r);
        upfather(k,l,r);
    }
    int search(int key,int *x,int k){
        int l=0,r=k-1;
        while(l<=r){
            int mid=(l+r)>>1;
            if(x[mid]==key)return mid;
            if(x[mid]>key)r=mid-1;
            else l=mid+1; 
        }
        return -1;
    }
    struct PP{
        int x,y;
        PP(){}
        PP(int xx,int yy){x=xx;y=yy;}
    }ans[maxn<<2];
    int solve(int tot,int up)//tot是边数 up是点数 
    {
        int upp=0,tans=0;
        for(int i=0;i<tot;i++){
            int R=search(s[i].h,a,up)-1;
            update(0,R,s[i].ju,1,0,up-1);
            if(sum[1]==tans)continue;
            if(ans[upp-1].x==s[i].x&&ans[upp-1].y==tans&&i!=0)upp--;
            else ans[upp++]=PP(s[i].x,tans);
            if(ans[upp-1].x==s[i].x&&ans[upp-1].y==sum[1]&&i!=0)upp--;
            else ans[upp++]=PP(s[i].x,sum[1]);
            tans=sum[1];
        }
        return upp;
    }
    int main()
    {
        int n,tot=0,l,h,r,up=0;
        a[up++]=0;
        scanf("%d",&n);
    //    while(~scanf("%d%d%d",&l,&h,&r))
        while(tot<n*2)
        {
            scanf("%d%d%d",&h,&l,&r);
            a[up++]=h;
            s[tot++]=P(l,h,-1);
            s[tot++]=P(r,h,1);
        }
        sort(a,a+up);
        sort(s,s+tot);
        up=unique(a,a+up)-a;
        int upp=solve(tot,up);
        printf("%d
    ",upp);
        for(int i=0;i<upp;i++)
        {
    //        if(i&1)printf("%d ",ans[i].y);
    //        else printf("%d ",ans[i].x);
             printf("%d% d
    ",ans[i].x,ans[i].y);
        }
    }

    2019-09-06 15:02:29

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

  • 相关阅读:
    《把时间当作朋友》读书笔记
    Oracle&SQLServer中实现跨库查询
    Android学习——界面互调2
    《IT不再重要》读后感
    Android学习——数据存储
    Android学习——编写菜单
    Android学习——后台程序
    Android学习——写个小实例
    Android学习——界面编程!
    深入理解JavaScript系列(42):设计模式之原型模式
  • 原文地址:https://www.cnblogs.com/kkkek/p/11462426.html
Copyright © 2020-2023  润新知