【題目鏈接】
http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=14795
【解題報告】
七月的時候做過掃描線線段樹,那個時候覺得好難TAT….
當然現在看是很基礎的題目了。
這道題根本上還是一個掃描線,不過由於是二維狀態下的掃描,所以裸的掃描線是不可取的。因爲那意味着O(n^2)的操作。(想想爲什麼不像一維的掃描線一樣只取一段區間的端點值?)
那我們能不能在某一維實現降低複雜度的操作呢?
容易想到,用線段樹來維護。這樣一維O(N),一維O(logN),可以愉快的掃描線了!
線段樹對我們來說是一個高效維護、查詢、修改線性數據的工具,所以凡是涉及到線性數據的題目,我們大可以都想想能不能使用線段樹來維護一下。
對矩形面積並來說就是如此,與其說是“線段樹掃描線”的這樣一個算法,倒不如說爲了維護掃描線算法,用到了線段樹操作。
線段樹有什麼用呢?
我們可以很快的來維護:掃描到當前線段時,多條線段的作用域疊加到一起的線段總長度。
那麼每次我們掃描到一條線段,直接拿線段總長,乘上當前線段和下條線段之間的高度。
對一個矩形來說,它的對邊,可以認爲是一條邊掃進,一條邊掃出。掃進的線段就線段樹update加上,掃出的線段就update減去。
然後我們對線段樹的每個節點維護的區間值表示:當前區間的線段總長度(注意,線段可不一定是連續線段)。
那麼每次維護之後我們只取線段樹的根節點的區間值即可。
另外,還有一個需要注意的點是,
我們在線段樹更新的時候,是這樣往下走的:
sumv[O]=sumv[O*2]+sumv[O*2+1].
如果左子樹維護的區間[l,mid],
右子樹維護的區間[mid+1,r]
那麼還有一個區間[mid,mid+1]沒有統計到,怎麼辦?
請讀者自行思考。
至於矩形面積並的求解方法,和掃描線的具體細節,以及如何把線段樹和掃描線結合,網上大量blog都有詳細清晰的說明。這裏不再贅述。如果讀者覺得理解起來喫力,代碼更是兩眼一抹黑,說明你像七月份的我一樣基礎非常不紮實,這時候強行看掃描線線段樹的效果並不好。所以請先去分別學習掃描線和線段樹的知識。
【參考代碼】
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn=1e5+50;
struct Type
{
double l,r,h; //以橫座標建立線段樹,縱座標爲高度
int cur; //cur=-1說明是出邊,cur=1說明是入邊
void sets( double x1, double x2, double h, int cur )
{
this->l=x1; this->r=x2; this->h=h; this->cur=cur;
}
bool operator < ( const Type& a )const
{
return h<a.h;
}
};
Type seg[maxn];
double sumv[maxn*4]; //用來維護線段並
int cntv[maxn*4]; //用來維護當前節點代表的區間被覆蓋了幾次
double point[maxn*2];//用來維護離散化後的端點
int n;
int uniq(int k)
{
int m=1;
sort( point+1,point+1+k );
for( int i=1;i<k;i++ )
{
if( point[i]!=point[i+1] )point[++m]=point[i+1];
}
return m;
}
void build( int O,int L,int R )
{
if(L==R){ sumv[O]=0; cntv[O]=0; }
else
{
int mid=(L+R)/2;
build( O*2,L,mid );
build( O*2+1,mid+1,R );
sumv[O]=0; cntv[O]=0;
}
}
void maintain( int O, int L, int R )
{
if( cntv[O] )
{
sumv[O]=point[R+1]-point[L];
}
else if( L<R )
{
sumv[O]=sumv[O*2]+sumv[O*2+1];
// cntv[O]=min( cntv[O*2],cntv[O*2+1] );
}
else { sumv[O]=0; cntv[O]=0; }
}
void pushdown( int O )
{
if( cntv[O] )
{
cntv[O*2]=cntv[O*2+1]=cntv[O];
cntv[O]=0;
sumv[O]=0;
}
}
void update( int O, int L, int R, int qL, int qR,int op )
{
if( qL<=L && R<=qR )
{
cntv[O]+=op;
}
else
{
// pushdown(O); //pushdown其實是不需要的,因爲我們遇到一個cnt就可以返回整段信息,也就是說,更深的cnt信息不需要考慮,所以cnt沒必要下傳
int mid=(L+R)/2;
if( qL<=mid )update( O*2,L,mid,qL,qR,op );
if( qR>mid )update( O*2+1,mid+1,R,qL,qR,op );
}
maintain( O,L,R );//重新計算sumv[O];
}
int main()
{
int kase=0;
while( ~scanf("%d",&n) &&n )
{
int k=0;
for( int i=1; i<=n; i++ )
{
double x1,x2,y1,y2;
scanf( "%lf%lf%lf%lf",&x1,&y1,&x2,&y2 );
seg[++k].sets( x1,x2,y1,-1 );
point[k]=x1;
seg[++k].sets( x1,x2,y2,1 );
point[k]=x2;
}
int m=uniq(k); //對端點進行了離散化
sort( seg+1,seg+1+k ); //所有k條線段從低到高排序
build( 1,1,m );
double ans=0;
for( int i=1; i<k; i++ )
{
int L=lower_bound( point+1,point+1+m,seg[i].l )-point;
int R=lower_bound( point+1,point+1+m,seg[i].r )-point-1;
update( 1,1,m,L,R,seg[i].cur );
ans+=sumv[1]*( seg[i+1].h-seg[i].h );
}
printf("Test case #%d\nTotal explored area: %.2lf\n\n",++kase,ans);
}
return 0;
}