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\n" ,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\nTotal explored area: %.2f\n\n" ,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\n" ,ans );
            
    }
    return 0;
} 

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

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

posted @ 2019-07-30 15:09  易如鱼  阅读(360)  评论(0编辑  收藏  举报