7.29 线段树扫描线 ,矩形扫描
经常有一种题是给你一个图形和二维平面上的一些点 ,问你在最多能圈住几个点
这个时候就是要用线段树+扫描线了
这个东西关键由以下内容构成 :
1.扫描序:
线段树维护的内容一般是每条x轴(或y轴上)在宽度限定范围内最多有几个点,要对点逐一扫描
扫描(updata)之前要根据扫描的顺序(从上到下或从左到右,与线段树存的数轴垂直的方向)对点排好序再进行扫描
2.点的正负权值/点的统计方法:
点的统计方法,类似于差分区间修改的方法,再设置正点的同时在其宽度(长度)限定边界设置一个负点,扫描到正点后进行计数
,扫描到其限定边界的负点后权值抵消,点的计数就自然减一
3.线段树
线段树可以很好地维护某一区间内点的数量(即权值之和),扫描序和点的正负保证了矩形的长,
线段树的更新区间宽度保证了矩形的宽,两者结合便能完成矩形扫描的任务
题意:在平面上有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了半天
这也是常见错误了 ,注意注意