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了半天

這也是常見錯誤了 ,注意注意


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com