掃描線
首先,掃描線是干什么的?掃描線一般運用在圖形上面,它和它的字面意思十分相似,就是一條線在整個圖上掃來掃去,它一般被用來解決圖形面積,周長等問題,以一道例題為例。給出n個正方形,這些正方形在平面直角坐標系中互相重疊擺放,但四條邊都與坐標軸平行,例如下圖所示。那么知道題目了,怎么運用呢?首先我們需要知道怎么用暴力解決這個問題,根據圖片可知圖中的面積是SABCD+SHEFG-SIDJE暴力搜索是個好東西,但是當數據范圍大了怎么辦?這里就要講到掃描線。


掃描線對於這道例題可以抽象為這四條紫色的直線(如上圖l1,l2,l3,l4),仔細觀察,可以看出這四條線把這個圖形分割成三個矩形,那么我們就可以直接求這三個矩形再加和是不是就可以了?那么現在難點來了,怎樣求這些矩形的面積。我們可以把題目中給的矩形的邊轉換成直線(如下圖),即只留下這四條邊,這四條線就是整個做法的核心。既然四條線已經看出來了,那么我們就可以一眼看出,面積就是從頭到現在的掃描線的重影減去已經結束的長方形的邊的投影承上兩條掃描線的間距。再把這些乘積加在一起。


下面就將如何實現了,首先我們可以想到用線段樹求區間和來求這些投影的長度,那么區間如此之大(-1e8~1e8),怎么能建樹呢?不會空間爆炸嗎?所以就應該運用動態開點線段樹,算一下每一個掃描線開一個節點,那么就是n個,一共有log21e8層所以是可以開的下的。根據這個說法,每一條邊應該進行排序,由於掃描是從左到右,所以排序應該是把橫坐標從小到大排序,所以每條邊有三個屬性:位置即橫坐標,從那個點開始,從那個點結束,這兩給點分別是縱坐標的兩個端點。
struct Line
{
int from,to,x,val;
}line[2001];
bool cmp1(const Line &a,const Line &b)
{
return a.x<b.x;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d%d%d%d",&a,&b,&c,&d);
line[i*2-1].x=a;
line[i*2-1].from=d+1;
line[i*2-1].to=b;
line[i*2-1].val=1;
line[i*2].x=c;
line[i*2].from=d+1;
line[i*2].to=b;
line[i*2].val=-1;
}
sort(line+1,line+1+2*n,cmp1);
}
掃描線排序
下面講解一下如何把線段樹運用進去,我們把每一條邊給定一個屬性,這個矩形的左面邊定義為入邊,給一個+1的值,右邊的邊定義為出邊,給一個-1的值,這就是邊里的val的意義,那么這個和線段樹又有什么關系呢?有了這個值我們可以快速地直接給線段樹賦值,讓其顯示是否有邊覆蓋在上面,也就是下面代碼中的cover數組的含義,如果cover數組有值不是零,那么這個區間就有邊,即有r-l+1的貢獻,否則則沒有。這便是查詢。在查詢中一定要查到葉子節點,因為在掃描線中是沒有上傳值或是下傳值的說法。
int find(int l,int r,int p)
{
if(cover[p])
return sum[p];
if(lp==rp&&rp==0)
return 0;
int sum=0;
if(lp!=0)
sum+=find(l,(l+r)>>1,lp);
if(rp!=0)
sum+=find(((l+r)>>1)+1,r,rp);
return sum;
}
掃描線查詢
最難的要數修改,如果現在的節點被現在要加的邊完全覆蓋,那么直接修改就好啦,否則要遞歸的尋找他的兒子,如果沒有兒子則動態開點出來,這邊是修改的想法,當然在修改時不要忘記修改cover的值。
void change(int l,int r,int x,int y,int &p,int delta)
{
if(!p)
p=++cnt;
if(x<=l&&r<=y)
{
cover[p]+=delta;
sum[p]=r-l+1;
return;
}
int mid=(l+r)>>1;
if(x<=mid)
change(l,mid,x,y,lp,delta);
if(y>mid)
change(mid+1,r,x,y,rp,delta);
}
掃描線修改
(新更)我們用這個代碼,思考一道題目,就是bzoj1645城市地平線,我們可以知道TLE這個事實,但是為什么呢?由於我們每一次我們都需要查詢,每一次查詢都是至少O(n)以上的時間復雜度,這樣我們整體的時間復雜度就是大於O(n^2),我們需要優化一下,我們可以開一個叫做sum的數組,也就是我們把上面的sum數組重新定義一下就可以了,在這里我們定義sum數組為當前區間之中所有的邊的和,那么我們很容易知道,如果當前的區間的cover值大於零,sum數組就位當前區間的r-l+1,如果cover等於零,sum數組的值就等於他的左右兒子的sum數組的值相加,是不是很簡單?根據定義整個圖形中的線段和就位跟的sum值。下面是更改后的區間修改以及pushup函數,當然由於我們可以O(1)求出整個圖形之中的線段和,所以find函數就沒有了。
void pushup(int p,int l,int r)
{
if(cover[p]>0) sum[p]=r-l+1;
else sum[p]=sum[lson[p]]+sum[rson[p]];
}
void change(int l,int r,int x,int y,int &p,int delta)
{
if(!p) p=++cnt;
if(x<=l&&r<=y)
{
cover[p]+=delta;
pushup(p,l,r);
return;
}
int mid=(l+r)>>1;
if(x<=mid) change(l,mid,x,y,lson[p],delta);
if(y>mid) change(mid+1,r,x,y,rson[p],delta);
pushup(p,l,r);
}
大致就是這樣,不會的可以評論發問題,我會解答。
