zoukankan      html  css  js  c++  java
  • 7.29 线段树扫描线 ,矩形扫描

    经常有一种题是给你一个图形和二维平面上的一些点 ,问你在最多能圈住几个点

    这个时候就是要用线段树+扫描线了

    这个东西关键由以下内容构成 :

    1.扫描序:

    线段树维护的内容一般是每条x轴(或y轴上)在宽度限定范围内最多有几个点,要对点逐一扫描

    扫描(updata)之前要根据扫描的顺序(从上到下或从左到右,与线段树存的数轴垂直的方向)对点排好序再进行扫描

    2.点的正负权值/点的统计方法:

    点的统计方法,类似于差分区间修改的方法,再设置正点的同时在其宽度(长度)限定边界设置一个负点,扫描到正点后进行计数

    ,扫描到其限定边界的负点后权值抵消,点的计数就自然减一

    3.线段树

    线段树可以很好地维护某一区间内点的数量(即权值之和),扫描序和点的正负保证了矩形的长,

    线段树的更新区间宽度保证了矩形的宽,两者结合便能完成矩形扫描的任务

    例题:HDU 5091---Beam Cannon

    题意:在平面上有n个点,现在有一个平行于坐标轴的矩形,宽为w 高为h,可以上下左右移动,但不能旋转,求这个矩形最多能够围住多少点?

    思路就是上面的思路:

    模板:

    #include<bits/stdc++.h>
    #include<cstdio>
    #include<iostream>
    #include<map>
    #include<algorithm>
    #include<queue>
    #include<vector>
    #include<set>
    #include<cstring>
    #include<cstdlib>
    #include<cmath>
    
    #define rep( i ,x ,y) for( int i=x ;i<=y ;i++)
    #define mem(a ,x) memset( a ,x ,sizeof(a) )
    #define N 10005
    #define pb(a) push_back(a)
    #define se second
    #define fi first
    #define lson l ,m ,pos<<1
    #define rson m+1,r ,pos<<1|1
    using namespace std;
    
    typedef long long ll;
    typedef  pair<int ,int> pii;
    typedef  pair<int ,ll>  pil;
    typedef  pair<ll ,ll>   pll;
    
    const ll mod = 1000000000+7;
    
    int  n ,w ,h; 
    
    struct Node{
        int x ,y ,v    ;
    }node[N<<1]; //positive and negertive
    
    struct TNode{
        int f ,m;         //f - lazy标记
    }tr[N<<4];
    
    bool cmp ( const Node & a ,const Node & b){
        return a.x == b.x && a.v > b.v || a.x < b.x;
    }
    
    void build(int l ,int r ,int pos){
        tr[pos].f = tr[pos].m = 0;
        if( l==r ) return ;
        int m = ( l+r )>>1;
        build( lson );
        build( rson );
    }
    
    void push_down( int i ){
        tr[i<<1].f += tr[i].f;
        tr[i<<1|1].f += tr[i].f;
        tr[i<<1].m += tr[i].f;
        tr[i<<1|1].m += tr[i].f;
        tr[i].f = 0;
    }
    
    void updata( int l ,int r ,int pos ,int t ){ //按点更新 
        if( l >=node[t].y && r<= node[t].y+h ){ //t的宽度限定更新区间的宽度
            tr[pos].f += node[t].v;
            tr[pos].m += node[t].v;
            return ;
        }
        if( tr[pos].f != 0)push_down( pos );
        int m = ( l+r )>>1;
        if( node[t].y <= m ) updata( lson ,t);
        if( node[t].y+h >  m ) updata( rson ,t);
        tr[pos].m = max( tr[pos<<1].m ,tr[pos<<1|1].m ); //统计左右区间哪个点多
    }
    
    int main( ){
        while( scanf("%d" ,&n) ,n>0 ){
            scanf( "%d%d" ,&w ,&h );
            rep( i ,1 ,n ){
                int x ,y;
                scanf("%d%d" ,&x ,&y );
                node[2*i-1].x = x;             // positive point
                node[2*i-1].y = y + 2*N;       // y 可能是负值
                node[2*i-1].v = 1;
                
                node[2*i].x = x+w;              //negitive point
                node[2*i].y = y + 2*N;
                node[2*i].v = -1;
            }
            
            sort( node +1 ,node + 2*n+1 ,cmp);
            build( 1 ,4*N ,1 );                 // blank tree
            
            int sum  =0;
            rep( i ,1 ,2*n ){
                updata( 1 ,4*N ,1 ,i );
                sum = max( sum ,tr[1].m );
            }
            printf( "%d
    " ,sum );
        }
        return 0;
    }

    矩形面积交的思路一样,线段树维护的当前扫描线上所有可统计边的长度,扫描线的关键部分依然是扫描序和正负权值

    值得注意的是,线段树更新时的参数是某一条线的l,r-1 而不是 l,r ,这是因为计算线段长度时是计算当前点与其之后的一个相邻点所致

    模板:

    #include<bits/stdc++.h>
    #define N 100
    #define rep( i ,x ,y) for( int i = x; i<= y ;i++ )
    #define mem( a ,x ) memset( a ,0 ,sizeof(a))
    #define lson l ,m ,pos<<1
    #define rson m+1 ,r ,pos<<1|1
    using namespace std;
    
    int n ,cnt ,Exist[ N<<4 ];
    double Sum[(100<<4) ] ,xy[(100<<2) +5];
    
    
    struct Square{               //node 's imformation
        int flag ,nx ,ny1 ,ny2;  // lisanhua id
        double x ,y1 ,y2;
        
        bool operator < (const Square & s ) const{
            return nx == s.nx && flag > s.flag || nx < s.nx;
        }
        
    } a[2*N+5];
    
    map<double ,int> p;  //离散化信息 
    map<int ,double> f;   //映射
    
    inline void push_up( int l,int r ,int pos ){
        if( Exist[pos] ) Sum[pos] = f[r+1] - f[l]; 
        //区间被整个覆盖的情况 ??为什么是r+1: 
        //因为算的是每一个点和其下面的相邻点的高度差,
        //和下面线段树搜索区间是 ny1~ny2-1 而不是 ny1~ny2是同样的原因 
        else if( l==r )  Sum[pos] = 0;  //区间长度为0
        else Sum[pos] = Sum[pos<<1] + Sum[pos<<1|1]; 
        //如果权值为0 , Sum[pos<<1] , Sum[pos<<1|1]之前没更新或已被更新为最新值
        //即不管这条边 ,将它更新为它之前其他边的值 
    } 
    
    inline void updata( int l ,int r ,int pos ,int L ,int R ,int v ){
        //cout<<"int tree : l "<<l<<" r "<<r<<" L "<<L<<"  R "<<R<<endl; 
        if( L > R )return;
        if( L <= l && r<= R){
            Exist[pos] += v;        //其实权值正负不重要,只要不为0就会加子区间长度 
            push_up( l ,r ,pos );
            return;
        }
        int m =(l+r) >>1;
        if( L <= m ) updata( lson ,L ,R ,v );
        if( R > m ) updata( rson ,L ,R ,v);
        push_up( l ,r ,pos );
    }
    
    int main( ){
        int ks = 1;
        double x1 ,x2 ,y1 ,y2;
        
        while( ~scanf( "%d" ,&n ) ,n){
        p.clear( ); f.clear( );    
        cnt = 0;
        
        rep( i ,1 ,n ){
            scanf("%lf%lf%lf%lf" ,&x1 ,&y1 ,&x2 ,&y2 );
            a[(i<<1)-1] = (Square) {1 ,0 ,0 ,0 ,x1 ,y1 ,y2 };
            a[(i<<1)] = (Square) {-1 ,0 ,0 ,0 ,x2 ,y1 ,y2 };
            xy[i<<2] = x1;     xy[(i<<2)-1] = y1;
            xy[(i<<2)-2] = x2; xy[(i<<2)-3] = y2; 
        }
        
        sort( xy+1 ,xy+(n<<2)+1 );
        rep( i ,1 ,n<<2 ) if( !p[xy[i]] )f[ p[xy[i]] = ++cnt ] = xy[i] ;//cout<<"p[xy[i]] "<<p[xy[i]]<<" "<<xy[i]<<endl;
        rep( i ,1 ,n<<1 ) a[i].nx = p[ a[i].x ] ,a[i].ny1 = p[ a[i].y1 ] ,a[i].ny2 = p[ a[i].y2 ] ;//cout<<"a[i].x "<<a[i].x<<" "<<a[i].nx<<endl;
        
        sort( a+1 ,a+1+(n<<1) );      //扫描序确认 
        mem( Exist , 0);
        mem( Sum ,0 );
        
        int Now = 1; double ans = 0;
        
        rep( i ,1 ,cnt ){
            //cout<<f[i]<<" "<<f[i-1]<<" "<<Sum[1]<<endl;
            ans += ( f[i]-f[i-1] )*Sum[1];     // Sum[1] tree s father
            if( a[Now].nx ^ i ) continue;
            while( a[Now].nx == i && Now<= (n<<1) )
            updata( 1 ,cnt ,1 ,a[Now].ny1 ,a[Now].ny2-1 ,a[Now].flag ),Now++; 
            // ny2-1 原因,计算高度差所需 ,这里计算高度差的方式是 f[i+1]-f[i] ,为了不超出原图形 ,就记做 ny2(上面那条边)-1 
        }
        printf( "Test case #%d
    Total explored area: %.2f
    
    " ,ks++, ans );
        }
        return 0;
        
    }

    注意离散化

    一道离散化的例题:

    题目链接

    http://acm.hdu.edu.cn/showproblem.php?pid=4007

    题意:给了n个点,求一个正方形能围住的最大点数,同样正方形平行于坐标轴;

    思路:与上面的题一样,但是这题数据范围很大,线段树的数组开不了这么大,那么必须要进行离散化;

    过程: 离散化倒没啥 ,扫描线又犯迷糊了

    扫描线关键:扫描序和点的正负保证了矩形的长,线段树的更新区间宽度保证了矩形的宽

    写的时候一定要想好自己准备写的线段树的方向和扫描序的方向,正负点的分布方向与扫描序方向相同 ,扫描序方向与线段树线段方向垂直

    写之前可以先在纸上确认好:扫描序 ,线段树的L和R ,正负点的方向 ,线段树的线段 这些方向是x轴还是y轴再写

    #include<bits/stdc++.h>
    #include<cstdio>
    #include<iostream>
    #include<map>
    #include<algorithm>
    #include<queue>
    #include<vector>
    #include<set>
    #include<cstring>
    #include<cstdlib>
    #include<cmath>
    
    #define rep( i ,x ,y) for( int i=x ;i<=y ;i++)
    #define mem(a ,x) memset( a ,x ,sizeof(a) )
    #define N 10005
    #define pb(a) push_back(a)
    #define se second
    #define fi first
    #define lson l ,m ,pos<<1
    #define rson m+1,r ,pos<<1|1
    using namespace std;
    
    typedef long long ll;
    typedef  pair<int ,int> pii;
    typedef  pair<int ,ll>  pil;
    typedef  pair<ll ,ll>   pll;
    
    const ll mod = 1000000000+7;
    struct node{
        ll v ,x ,y;
        int idx ,idxx ,idy;
        
        bool operator < ( const node & s ) const{       
            //扫描序与树的方向垂直
            //扫描序: y轴 
            return idy == s.idy && v>s.v || idy < s.idy;
        } 
    } a[N];
    
    ll sum[N<<4] ,lazy[N<<4] ,xy[N<<2];
    ll n ,r;
    
    inline void push_down(int pos){
        //cout<<" pd "<<pos <<endl;
        sum[pos<<1] += lazy[pos];
        sum[ pos<<1|1 ] += lazy[pos];
        lazy[pos<<1] += lazy[pos];
        lazy[pos<<1|1] += lazy[pos];
        lazy[pos] = 0;
        return ;
    }
    
    inline void push_up(int pos){
        sum[pos] = max( sum[pos<<1] ,sum[pos<<1|1] );
    }
    
    inline void build( int l ,int r ,int pos ){
       sum[pos] = lazy[pos] = 0;
       int m =( l +r )>>1;
       if( l == r )return;
       build( lson );
       build( rson );
    }
    
    inline void updata( int l ,int r ,int pos ,int L ,int R ,int v ){
        //cout<<" l "<<l<<" r "<<r<<" xyl "<<xy[l]<<" xyr "<<xy[r]<<endl;
        if( l >= L && r <= R ){
            sum[pos] += v;
            lazy[pos] += v;
            return;
        }    
        if( sum[pos] > 0)push_down( pos );
        int m = (l+r)>>1;
        if( m >= L)updata( lson ,L ,R ,v);
        if( m < R)updata( rson ,L ,R ,v);
        push_up( pos );
        //cout<<sum[pos]<<endl;
    }
    
    map<ll ,int> mp;
    int main( ){
        while( ~scanf("%lld%lld" ,&n ,&r) ){
        mp.clear( );
            rep( i ,1 ,n ){
            scanf("%lld%lld" ,&a[i<<1].x ,&a[i<<1].y);
            a[i<<1].v= 1;
            a[(i<<1)-1].v = -1;
            a[(i<<1)-1].x = a[i<<1].x;
            a[(i<<1)-1].y = a[i<<1].y + r;
            xy[ 4*i ] =  a[i<<1].x;
            xy[ 4*i-1 ] = a[i<<1].y;
            xy[ 4*i-2 ] = a[i<<1].y + r;
            xy[ 4*i-3 ] = a[i<<1].x + r;
            }
            //离散化 
            sort( xy +1 ,xy +1 +(n*4) );
            int sz = unique(xy +1 ,xy +1 +(n*4))-xy-1 ,cnt = 0;
            rep( i ,1 ,sz )mp[ xy[i] ] = i ;
            
            rep( i ,1 ,n<<1)a[i].idx = mp[a[i].x] ,a[i].idxx = mp[a[i].x+r] ,a[i].idy = mp[a[i].y]; 
            // idx--L ,idxx--R ,LR即线段树方向 ,x轴 
            sort( a+1 ,a+1+(n<<1) );
            
            build( 1 ,sz ,1 );
            ll ans = 0;
            rep( i ,1 ,n<<1 ){
                //cout<< " L " << a[i].idx<<" R "<<a[i].idxx<<endl;
                //cout<<" xyL "<<xy[a[i].idx]<< " xyR "<<xy[a[i].idxx]<<" v "<<a[i].v<<endl;
                updata( 1 ,sz ,1 ,a[i].idx, a[i].idxx ,a[i].v );
                ans = max( ans ,sum[1] );
            }
            printf("%lld
    " ,ans );
                
        }
        return 0;
    } 

    还有就是线段树的push_down关于lazy的更新又写错了(lazy[son] += lazy[pos] 错写成 lazy[son] = lazy[pos]),debug了半天

    这也是常见错误了 ,注意注意

  • 相关阅读:
    Android 下压缩图片—微弱失真
    android中ViewHolder通用简洁写法
    Android裁剪固定大小头像的功能
    Android进度加载的Loading效果
    GitHub开源项目总结
    Android 实现emoji表情的demo
    vim 高级使用技巧第二篇
    android apk 防止反编译技术第一篇-加壳技术
    Android Metro风格的Launcher开发系列第二篇
    FFMPEG高级编程第一篇:环境搭建及编译
  • 原文地址:https://www.cnblogs.com/-ifrush/p/11266464.html
Copyright © 2011-2022 走看看