我把03年集訓隊王知昆的論文搬上來遼算法
王知昆《淺談用極大化思想解決最大子矩形問題》編程
【摘要】app
本文針對一類近期常常出現的有關最大(或最優)子矩形及相關變形問題,介紹了極大化思想在這類問題中的應用。分析了兩個具備必定通用性的算法。並經過一些例題講述了這些算法選擇和使用時的一些技巧。dom
【關鍵字】矩形,障礙點,極大子矩形函數
最大子矩形問題:在一個給定的矩形網格中有一些障礙點,要找出網格內部不包含任何障礙點,且邊界與座標軸平行的最大子矩形。優化
首先明確一些概念。3d
一、定義有效子矩形爲內部不包含任何障礙點且邊界與座標軸平行的子矩形。如圖所示,第一個是有效子矩形(儘管邊界上有障礙點),第二個不是有效子矩形(由於內部含有障礙點)。
code
二、極大有效子矩形:一個有效子矩形,若是不存在包含它且比它大的有效子矩形,就稱這個有效子矩形爲極大有效子矩形。(爲了敘述方便,如下稱爲極大子矩形)htm
三、定義最大有效子矩形爲全部有效子矩形中最大的一個(或多個)。如下簡稱爲最大子矩形。
【定理1】在一個有障礙點的矩形中的最大子矩形必定是一個極大子矩形。
證實:若是最大子矩形A不是一個極大子矩形,那麼根據極大子矩形的定義,存在一個包含A且比A更大的有效子矩形,這與「A是最大子矩形」矛盾,因此【定理1】成立。
定理1雖然很顯然,但倒是很重要的。根據定理1,咱們能夠獲得這樣一個解題思路:經過枚舉全部的極大子矩形,就能夠找到最大子矩形。下面根據這個思路來設計算法。
約定:爲了敘述方便,設整個矩形的大小爲n×m,其中障礙點個數爲s。
時間複雜度:O(S2)
空間複雜度:O(S)
算法的思路是經過枚舉全部的極大子矩形找出最大子矩形。根據這個思路能夠發現,若是算法中有一次枚舉的子矩形不是有效子矩形、或者不是極大子矩形,那麼能夠確定這個算法作了「無用功」,這也就是須要優化的地方。怎樣保證每次枚舉的都是極大子矩形呢,咱們先從極大子矩形的特徵入手。
【定理2】:一個極大子矩形的四條邊必定都不能向外擴展。更進一步地說,一個有效子矩形是極大子矩形的充要條件是這個子矩形的每條邊要麼覆蓋了一個障礙點,要麼與整個矩形的邊界重合。
定理2的正確性很顯然,若是一個有效子矩形的某一條邊既沒有覆蓋一個障礙點,又沒有與整個矩形的邊界重合,那麼確定存在一個包含它的有效子矩形。根據定理2,咱們能夠獲得一個枚舉極大子矩形的算法。爲了處理方便,首先在障礙點的集合中加上整個矩形四角上的點。每次枚舉子矩形的上下左右邊界(枚舉覆蓋的障礙點),而後判斷是否合法(內部是否有包含障礙點)。這樣的算法時間複雜度爲O(S5),顯然過高了。考慮到極大子矩形不能包含障礙點,所以這樣枚舉4個邊界顯然會產生大量的無效子矩形。
考慮只枚舉左右邊界的狀況。對於已經肯定的左右邊界,能夠將全部處在這個邊界內的點按從上到下排序,如圖1中所示,每一格就表明一個有效子矩形。這樣作時間複雜度爲O(S3)。因爲確保每次獲得的矩形都是合法的,因此枚舉量比前一種算法小了不少。但須要注意的是,這樣作枚舉的子矩形雖然是合法的,然而不必定是極大的。因此這個算法還有優化的餘地。經過對這個算法不足之處的優化,咱們能夠獲得一個高效的算法。
回顧上面的算法,咱們不難發現,所枚舉的矩形的上下邊界都覆蓋了障礙點或者與整個矩形的邊界重合,問題就在於左右邊界上。只有那些左右邊界也覆蓋了障礙點或者與整個矩形的邊界重合的有效子矩形纔是咱們須要考察的極大子矩形,因此前面的算法作了很多「無用功」。怎麼減小「無用功」呢,這裏介紹一種算法(算法1),它能夠用在很多此類題目上。
算法的思路是這樣的,先枚舉極大子矩形的左邊界,而後從左到右依次掃描每個障礙點,並不斷修改可行的上下邊界,從而枚舉出全部以這個定點爲左邊界的極大子矩形。考慮如圖2中的三個點,如今咱們要肯定全部以1號點爲左邊界的極大矩形。先將1號點右邊的點按橫座標排序。而後按從左到右的順序依次掃描1號點右邊的點,同時記錄下當前的可行的上下邊界。
開始時令當前的上下邊界分別爲整個矩形的上下邊界。而後開始掃描。第一次遇到2號點,以2號點做爲右邊界,結合當前的上下邊界,就獲得一個極大子矩形(如圖3)。同時,因爲所求矩形不能包含2號點,且2號點在1號點的下方,因此須要修改當前的下邊界,即以2號點的縱座標做爲新的下邊界。第二次遇到3號點,這時以3號點的橫座標做爲右邊界又能夠獲得一個知足性質1的矩形(如圖4)。
相似的,須要相應地修改上邊界。以此類推,若是這個點是在當前點(肯定左邊界的點)上方,則修改上邊界;若是在下方,則修改下邊界;若是處在同一行,則可停止搜索(由於後面的矩形面積都是0了)。因爲已經在障礙點集合中增長了整個矩形右上角和右下角的兩個點,因此不會遺漏右邊界與整個矩形的右邊重合的極大子矩形(如圖5)。
須要注意的是,若是掃描到的點不在當前的上下邊界內,那麼就不須要對這個點進行處理。
這樣作是否將全部的極大子矩形都枚舉過了呢?能夠發現,這樣作只考慮到了左邊界覆蓋一個點的矩形,所以咱們還須要枚舉左邊界與整個矩形的左邊界重合的狀況。這還能夠分爲兩類狀況。一種是左邊界與整個舉行的左邊界重合,而右邊界覆蓋了一個障礙點的狀況,對於這種狀況,能夠用相似的方法從右到左掃描每個點做爲右邊界的狀況。另外一種是左右邊界均與整個矩形的左右邊界重合的狀況,對於這類狀況咱們能夠在預處理中完成:先將全部點按縱座標排序,而後能夠獲得以相鄰兩個點的縱座標爲上下邊界,左右邊界與整個矩形的左右邊界重合的矩形,顯然這樣的矩形也是極大子矩形,所以也須要被枚舉到。
經過前面兩步,能夠枚舉出全部的極大子矩形。算法1的時間複雜度是O(S2)。這樣,能夠解決大多數最大子矩形和相關問題了。
雖然以上的算法(算法1)看起來是比較高效的,但也有使用的侷限性。能夠發現,這個算法的複雜度只與障礙點的個數s有關。但對於某些問題,s最大有可能達到n×m,當s較大時,這個算法就未必能知足時間上的要求了。可否設計出一種依賴於n和m的算法呢?這樣在算法1不能奏效的時候咱們還有別的選擇。咱們再從新從最基本的問題開始研究。
時間複雜度:O(NM)
空間複雜度:O(S)
首先,根據定理1:最大有效子矩形必定是一個極大子矩形。不過與前一種算法不一樣的是,咱們再也不要求每一次枚舉的必定是極大子矩形而只要求全部的極大子矩形都被枚舉到。看起來這種算法可能比前一種差,其實否則,由於前一種算法並非完美的:雖然每次考察的都是極大子矩形,但它仍是作了必定量的「無用功」。能夠發現,當障礙點很密集的時候,前一種算法會作大量沒用的比較工做。要解決這個問題,咱們必須跳出前面的思路,從新考慮一個新的算法。注意到極大子矩形的個數不會超過矩形內單位方格的個數,所以咱們有可能找出一種時間複雜度是O(N×M)的算法。
定義:
有效豎線:除了兩個端點外,不覆蓋任何障礙點的豎直線段。
懸線:上端點覆蓋了一個障礙點或達到整個矩形上端的有效豎線。如圖所示的三個有效豎線都是懸線。
對於任何一個極大子矩形,它的上邊界上要麼有一個障礙點,要麼和整個矩形的上邊界重合。那麼若是把一個極大子矩形按x座標不一樣切割成多個(其實是無數個)與y軸垂直的線段,則其中必定存在一條懸線。並且一條懸線經過儘量地向左右移動剛好能獲得一個子矩形(未必是極大子矩形,但只可能向下擴展)。經過以上的分析,咱們能夠獲得一個重要的定理。
【定理3】:若是將一個懸線向左右兩個方向儘量移動所獲得的有效子矩形稱爲這個懸線所對應的子矩形,那麼全部懸線所對應的有效子矩形的集合必定包含了全部極大子矩形的集合。
定理3中的「儘量」移動指的是移動到一個障礙點或者矩形邊界的位置。
根據【定理3】能夠發現,經過枚舉全部的懸線,就能夠枚舉出全部的極大子矩形。因爲每一個懸線都與它底部的那個點一一對應,因此懸線的個數=(n-1)×m(以矩形中除了頂部的點之外的每一個點爲底部,均可以獲得一個懸線,且沒有遺漏)。若是能作到對每一個懸線的操做時間都爲O(1),那麼整個算法的複雜度就是O(NM)。這樣,咱們看到了解決問題的但願。
如今的問題是,怎樣在O(1)的時間內完成對每一個懸線的操做。咱們知道,每一個極大子矩形均可以經過一個懸線左右平移獲得。因此,對於每一個肯定了底部的懸線,咱們須要知道有關於它的三個量:頂部、左右最多能移動到的位置。對於底部爲(i,j)的懸線,設它的高爲hight[i,j],左右最多能移動到的位置爲left[i,j],right[i,j]。爲了充分利用之前獲得的信息,咱們將這三個函數用遞推的形式給出。
對於以點(i,j)爲底部的懸線:
若是點(i-1,j)爲障礙點,那麼,顯然以(i,j)爲底的懸線高度爲1,並且左右都可以移動到整個矩形的左右邊界,即
若是點(i-1,j)不是障礙點,那麼,以(i,j)爲底的懸線就等於以(i-1,j)爲底的懸線+點(i,j)到點(i-1,j)的線段。所以,height[i,j]=height[i-1,j]+1。比較麻煩的是左右邊界,先考慮left[i,j]。以下圖所示,(i,j)對應的懸線左右能移動的位置要在(i-1,j)的基礎上變化。
即left[i,j]=max
right[i,j]的求法相似。綜合起來,能夠獲得這三個參數的遞推式:
這樣作充分利用了之前獲得的信息,使每一個懸線的處理時間複雜度爲O(1)。對於以點(i,j)爲底的懸線對應的子矩形,它的面積爲(right[i,j]-left[i,j])*height[i,j]。
這樣最後問題的解就是:
Result=max
整個算法的時間複雜度爲O(NM),空間複雜度是O(NM)。
以上說了兩種具備必定通用性的處理算法,時間複雜度分別爲O(S2)和O(NM)。兩種算法分別適用於不一樣的狀況。從時間複雜度上來看,第一種算法對於障礙點稀疏的狀況比較有效,第二種算法則與障礙點個數的多少沒有直接的關係(固然,障礙點較少時能夠經過對障礙點座標的離散化來減少處理矩形的面積,不過這樣比較麻煩,不如第一種算法好),適用於障礙點密集的狀況。
分析:
題目的數學模型就是給出一個矩形和矩形中的一些障礙點,要求出矩形內的最大有效子矩形。這正是咱們前面所討論的最大子矩形問題,所以前兩種算法都適用於這個問題。
下面分析兩種算法運用在本題上的優略:
對於第一種算法,不用加任何的修改就能夠直接應用在這道題上,時間複雜度爲O(S2),S爲障礙點個數;空間複雜度爲O(S)。
對於第二種算法,須要先作必定的預處理。因爲第二種算法複雜度與牛場的面積有關,而題目中牛場的面積很大(30000×30000),所以須要對數據進行離散化處理。離散化後矩形的大小降爲S×S,因此時間複雜度爲O(S2),空間複雜度爲O(S)。說明:須要注意的是,爲了保證算法能正確執行,在離散化的時候須要加上S個點,所以實際須要的時間和空間較大,並且編程較複雜。
從以上的分析來看,不管從時空效率仍是編程複雜度的角度來看,這道題採用第一種算法都更優秀。
program happy; var f:text; x,y:array[1..5002] of longint; maxl,n,best,a,b,c,w,l,i,j,high,low:longint; procedure sort(l,r:longint); var i,j:longint; begin i:=l+random(r-l+1); a:=x[i]; b:=y[i]; i:=l; j:=r; repeat while (x[i] a) or ((x[j]=a) and (y[j]>b)) do j:=j-1; if i<=j then begin c:=x[i]; x[i]:=x[j]; x[j]:=c; c:=y[i]; y[i]:=y[j]; y[j]:=c; inc(i); dec(j); end; until i>j; if j>l then sort(l,j); if ia) do j:=j-1; if i<=j then begin c:=y[i]; y[i]:=y[j]; y[j]:=c; inc(i); dec(j); end; until i>j; if j>l then sort_y(l,j); if i best then best:=a; end; begin assign(f,'happy.in'); reset(f); readln(f,l,w); readln(f,n); for i:=1 to n do readln(f,x[i],y[i]); close(f); inc(n); x[n]:=l; y[n]:=0; inc(n); x[n]:=0; y[n]:=w; sort(1,n); best:=0; for i:=1 to n do begin high:=w; low:=0; maxl:=l-x[i]; for j:=i+1 to n do if (y[j]<=high) and (y[j]>=low) then begin if maxl*(high-low)<=best then break; max((x[j]-x[i])*(high-low)); if y[j]=y[i] then break else if y[j]>y[i] then if y[j] low then low:=y[j]; end; high:=w; low:=0; maxl:=l-x[i]; for j:=i-1 downto 1 do if (y[j]<=high) and (y[j]>=low) then begin if maxl*(high-low)<=best then break; max((x[i]-x[j])*(high-low)); if y[j]=y[i] then break else if y[j]>y[i] then if y[j] low then low:=y[j]; end; end; sort_y(1,n); for i:=1 to n-1 do max((y[i+1]-y[i])*l); writeln(best); end.
題意簡述:(原題見論文附件)
一個被分爲 n*m 個格子的糖果盒,第 i 行第 j 列位置的格子裏面有 a [i,j] 顆糖。但糖果盒的一些格子被老鼠洗劫。如今須要儘快從這個糖果盒裏面切割出一個矩形糖果盒,新的糖果盒不能有洞,而且但願保留在新糖果盒內的糖的總數儘可能多。
參數約定:1 ≤ n,m ≤ 1000
分析
首先須要注意的是:本題的模型是一個矩陣,而不是矩形。在矩陣的狀況下,因爲點的個數是有限的,因此又產生了一個新的問題:最大權值子矩陣。
定義:
有效子矩陣爲內部不包含任何障礙點的子矩形。與有效子矩形不一樣,有效子矩陣地邊界上也不能包含障礙點。
有效子矩陣的權值(只有有效子矩形纔有權值)爲這個子矩陣包含的全部點的權值和。
最大權值有效子矩陣爲全部有效子矩陣中權值最大的一個。如下簡稱爲最大權值子矩陣。
本題的數學模型就是正權值條件下的最大權值子矩陣問題。再一次利用極大化思想,由於矩陣中的權值都是正的,因此最大權值子矩陣必定是一個極大子矩陣。因此咱們只須要枚舉全部的極大子矩陣,就能從中找到最大權值子矩陣。一樣,兩種算法只需稍加修改就能夠解決本題。下面分析兩種算法應用在本題上的優略:
對於第一種算法,因爲矩形中障礙點的個數是不肯定的,並且最大有可能達到N×M,這樣時間複雜度有可能達到O(N2M2),空間複雜度爲O(NM)。此外,因爲矩形與矩陣的不一樣,因此在處理上會有一些小麻煩。
對於第二種算法,稍加變換就能夠直接使用,時間複雜度爲O(NM),空間複雜度爲O(NM)。
能夠看出,第一種算法並不適合這道題,所以最好仍是採用第二種算法。
program candy; const maxn=1000; var left,right,high:array[1..maxn] of longint; s:array[0..maxn,0..maxn] of longint; now,res,leftmost,rightmost,i,j,k,n,m:longint; f:text; begin assign(f,'candy.in'); reset(f); readln(f,n,m); fillchar(s,sizeof(s),0); for i:=1 to m do begin left[i]:=1; right[i]:=m; high[i]:=0; end; res:=0; for i:=1 to n do begin k:=0; leftmost:=1; for j:=1 to m do begin read(f,now); k:=k+now; s[i,j]:=s[i-1,j]+k; if now=0 then begin high[j]:=0; left[j]:=1; right[j]:=m; leftmost:=j+1; end else begin high[j]:=high[j]+1; if leftmost>left[j] then left[j]:=leftmost; end; end; rightmost:=m; for j:=m downto 1 do begin if high[j]=0 then begin rightmost:=j-1; end else begin if right[j]>rightmost then right[j]:=rightmost; now:=s[i,right[j]]+s[i-high[j],left[j]-1]-s[i-high[j],right[j]]-s[i,left[j]-1]; if now>res then res:=now; end; end; end; writeln(res); end.
題意簡述(原題見論文附件)
Farmer John想在他的正方形農場上建一個正方形穀倉。因爲農場上有一些樹,並且Farmer John又不想砍這些樹,所以要找出最大的一個不包含任何樹的一塊正方形場地。每棵樹均可以當作一個點。
參數約定:牛場爲N×N的,樹的棵數爲T。N≤1000,T≤10000。
program BigBarn; var d:array[1..1000,1..1000] of longint; height,left,right:array[1..1000] of longint; leftmost,rightmost,res,i,j,k,t,n:longint; f:text; begin assign(f,'bigbrn.in'); reset(f); readln(f,n,t); fillchar(d,sizeof(d),0); for i:=1 to t do begin readln(f,j,k); d[j,k]:=1; end; close(f); for i:=1 to n do begin height[i]:=0; left[i]:=1; right[i]:=n; end; res:=0; for i:=1 to n do begin leftmost:=1; for j:=1 to n do if d[i,j]=1 then begin height[j]:=0; left[j]:=1; right[j]:=n; leftmost:=j+1; end else begin height[j]:=height[j]+1; if leftmost>left[j] then left[j]:=leftmost; end; rightmost:=n; for j:=n downto 1 do if d[i,j]=1 then rightmost:=j-1 else begin if rightmostres then res:=k; end; end; assign(f,'bigbrn.out'); rewrite(f); writeln(f,res); close(f); end.
分析:
這題是矩形上的問題,但要求的是最大子正方形。首先,明確一些概念。
一、定義有效子正方形爲內部不包含任何障礙點的子正方形
二、定義極大有效子正方形爲不能再向外擴展的有效子正方形,一下簡稱極大子正方形
三、定義最大有效子正方形爲全部有效子正方形中最大的一個(或多個),如下簡稱最大子正方形。
本題的模型有一些特殊,要在一個含有一些障礙點的矩形中求最大子正方形。這與前兩題的模型是否有類似之處呢?仍是從最大子正方形的本質開始分析。
與前面的狀況相似,利用極大化思想,咱們能夠獲得一個定理:
【定理4】:在一個有障礙點的矩形中的最大有效子正方形必定是一個極大有效子正方形。
根據【定理4】,咱們只須要枚舉出全部的極大子正方形,就能夠從中找出最大子正方形。極大子正方形有什麼特徵呢?所謂極大,就是不能再向外擴展。若是是極大子矩形,那麼不能再向外擴展的充要條件是四條邊上都覆蓋了障礙點(【定理2】)。相似的,咱們能夠知道,一個有效子正方形是極大子正方形的充要條件是它任何兩條相鄰的邊上都覆蓋了至少一個障礙點。根據這一點,能夠獲得一個重要的定理。
【定理5】:每個極大子正方形都至少被一個極大子矩形包含。且這個極大子正方形必定有兩條不相鄰的邊與這個包含它的極大子矩形的邊重合。
根據【定理5】,咱們只須要枚舉全部的極大子矩形,並檢查它所包含的極大子正方形(一個極大子矩形包含的極大子正方形都是同樣大的)是不是最大的就能夠了。這樣,問題的實質和前面所說的最大子矩形問題是同樣的,一樣的,所採用的算法也是同樣的。
由於算法1和算法2都枚舉出了全部的極大子矩形,所以,算法1和算法2均可以用在本題上。具體的處理方法以下:對於每個枚舉出的極大子矩形,如圖所示,若是它的邊長爲a、b,那麼它包含的極大子正方形的邊長即爲min(a,b)。
考慮到N和T的大小不一樣,因此不一樣的算法會有不一樣的效果。下面分析兩種算法應用在本題上的優略。
對於第一種算法,時間複雜度爲O(T2),對於第二種算法,時間複雜度爲O(N2)。由於N<T,因此從時間複雜度的角度看,第二種算法要比第一種算法好。考慮到兩個算法的空間複雜度均可以承受,因此選擇第二種算法較好些。
如下是第一種和第二種算法編程實現後在USACO Training Program Gateway上的運行時間。能夠看出,在數據較大時,算法2的效率比算法1高。
以上,利用極大化思想和前面設計的兩個算法,經過轉換模型,解決了三個具備必定表明性的例題。解題的關鍵就是如何利用極大化思想進行模型轉換和如何選擇算法。
設計算法要從問題的基本特徵入手,找出解題的突破口。本文介紹了兩種適用於大部分最大子矩形問題及相關變型問題的算法,它們設計的突破口就是利用了極大化思想,找到了枚舉極大子矩形這種方法。
在效率上,兩種算法對於不一樣的狀況各有千秋。一個是針對障礙點來設計的,所以複雜度與障礙點有關;另外一個是針對整個矩形來設計的,所以複雜度與矩形的面積有關。雖然兩個算法看起來有着巨大的差異,但他們的本質是相通的,都是利用極大化思想,從枚舉全部的極大有效子矩形入手,找出解決問題的方法。
須要注意的是,在解決實際問題是僅靠套用一些現有算法是不夠的,還須要對問題進行全面、透徹的分析,找出解題的突破口。
此外,若是採用極大化思想,前面提到的兩種算法的複雜度已經不能再下降了,由於極大有效子矩形的個數就是O(NM)或O(S2)的。若是採用其餘算法,理論上是有可能進一步提升算法效率,下降複雜度的。