考慮這樣乙個問題:$n\times m$的平面上有k個點(座標都是整數),求乙個面積最大的邊平行於座標軸的矩形,使得其內部不包含任何乙個給出的點(但是可以在邊界上)。就比如說下面這幅圖:
紅色的點是給出的點,那麼黑色框框表示的這個矩形就是要求的最大子矩形啦。
最暴力的做法:提前預處理出乙個二維部分和,然後列舉左上角的頂點和右下角的頂點每次判斷是否合法並用面積更新答案就可以了。複雜度是$o(n^2m^2)$的。這太大了,不好接受。
考慮進行優化。
假如現在我們定下來上下兩條邊界,列舉右邊界,那麼向左一定會延伸到第乙個障礙點(否則一定不是最優的)。那麼我們省去了一維列舉,複雜度可以降為$o(n^nm)$
但是這還是不夠爽啊,我們希望得到的複雜度是$o(nm)$。
既然這樣,我們只有兩種思路:列舉兩個邊界,然後每次用常數的時間求出最優解,或是列舉一側邊界,每次用$o(n)$的時間求出最優解。考慮第一種思路。如果列舉的是相對的兩側的話,那麼剛剛我們已經找到了$o(n)$找出最優解的方法,很難想到更優的方法。那麼就試圖列舉相鄰的兩側(右側和上側)。套用剛剛的想法,我們試圖找到左下角最近的障礙。但由於是二維的狀態,所以問題變得很困難,gg
那麼只剩下列舉一條邊界的思路的。假設我們列舉的是上邊界。由於列舉的維數變少,所以我們希望預處理得到的資訊更多。考慮一下關於每條上邊界,我們能預處理出什麼。很顯然的我們可以輕鬆地得到邊界上每個點向下最遠延伸多少,像下圖這樣:
黃線是我們列舉的上邊界。延伸下來的就是它能延伸的最下方(沒畫出來的位置表示可以一直延伸到圖形底端)。這個東西可以很容易預處理出來。如果左右邊界確定的話,下邊界一定是這一段裡向下延伸距離最小的了。那麼問題就變成了$o(n)$的時間求出一段數中最大的長度*rmq。用單調棧可以預處理出每個數左邊第乙個比它小的和右邊第乙個比它小的。具體做法是這樣的:維護乙個單調遞增的棧,每次進棧時的原棧頂就是答案,時間複雜度$o(n)$。然後只需要列舉rmq是哪個就可以了,再乘以它左右延伸的長度。
總結:這個東西我們用到的最主要的性質就是座標有界且都是整數。考慮通過預處理來減少列舉的維數,從最初的暴力逐步優化成乙個$o(n^2)$的優秀演算法。
例題:bzoj1057棋盤製作:
題目大意:給出乙個n*m的黑白棋盤,求出乙個面積最大的子矩形和面積最大的子正方形使得它們內部相鄰的兩格不同色。
第一步是求出乙個標準的棋盤(n*m且相鄰兩格不同色的棋盤),然後將給出的棋盤與之異或。(這樣可以放在一起的就變成了一塊連續的1或0),然後問題就化歸為了上面的模型。面積最大的子正方形只需要求出所有合法矩形中較小邊最大的那個就行了。
1view code//date 20140703
2 #include 3 #include 4
5const
int maxn = 2050;6
7 inline int
getint()812
return
ans;13}
1415 inline int innew(int &a, int b) return0;}
16 inline int min(int a, int b)
1718
intn, m;
19int
map[maxn][maxn], down[maxn][maxn], stack[maxn], lft[maxn], rgt[maxn];
20int
anssqr, ansrec;
2122
intmain()
2333
printf("\n");
*/34
//calc 0
35 memset(down, 0, sizeof
down);
36for(int i = 1; i <= m; ++i) down[n][i] = !map[n][i];
37for(int i = n - 1; i; --i) for(int j = 1; j <= m; ++j) if(!map[i][j]) down[i][j] = down[i + 1][j] + 1;38
39/*
for(int i = 1; i <= n; ++i)
4044
printf("\n");
45*/
46for(int i = 1; i <= n; ++i)
4752
while(stop && down[i][stack[stop]] >= down[i][j]) --stop;
53 lft[j] = stop ?stack[stop] : lastzero;
54 stack[++stop] =j;55}
56 stop = 0; lastzero = m + 1;57
for(int j = m; j; --j)
5860
while(stop && down[i][stack[stop]] >= down[i][j]) --stop;
61 rgt[j] = stop ?stack[stop] : lastzero;
62 stack[++stop] =j;63}
6465
//for(int j = 1; j <= m; ++j) printf("%d ", lft[j]); printf("\n");
66//
for(int j = 1; j <= m; ++j) printf("%d ", rgt[j]); printf("\n");
6768
for(int j = 1; j <= m; ++j) if
(down[i][j])
6973}74
75//
printf("%d\n%d\n", anssqr * anssqr, ansrec);
7677
//calc 1
7879 memset(down, 0, sizeof
down);
80for(int i = 1; i <= m; ++i) down[n][i] =map[n][i];
81for(int i = n - 1; i; --i) for(int j = 1; j <= m; ++j) if(map[i][j]) down[i][j] = down[i + 1][j] + 1;82
83/*
84for(int i = 1; i <= n; ++i)
8589
printf("\n");
90*/
91for(int i = 1; i <= n; ++i)
9297
while(stop && down[i][stack[stop]] >= down[i][j]) --stop;
98 lft[j] = stop ?stack[stop] : lastzero;
99 stack[++stop] =j;
100}
101 stop = 0; lastzero = m + 1
;102
for(int j = m; j; --j)
103105
while(stop && down[i][stack[stop]] >= down[i][j]) --stop;
106 rgt[j] = stop ?stack[stop] : lastzero;
107 stack[++stop] =j;
108}
109//
for(int j = 1; j <= m; ++j) printf("%d ", lft[j]); printf("\n");
110//
for(int j = 1; j <= m; ++j) printf("%d ", rgt[j]); printf("\n");
111112
for(int j = 1; j <= m; ++j) if
(down[i][j])
113117
}118
119 printf("
%d\n%d\n
", anssqr *anssqr, ansrec);
120return0;
121 }
最大矩形(單調佇列)
給乙個直方圖,求直方圖中的最大矩形的面積。例如,下面這個中直方圖的高度從左到右分別是2,1,4,5,1,3,3,他們的寬都是1,其中最大的矩形是陰影部分。輸入包含多組資料。每組資料用乙個整數n來表示直方圖中小矩形的個數,你可以假定1 n 100000.然後接下來n個整數h1,hn,滿足 0 hi 1...
最大矩形求解問題(單調遞減棧)
在這裡,如果相乘的不是個數而是區間元素的和,那麼就是該問題的變體,其實質是等價的。如果利用常規演算法,以每乙個為元素為中心左右搜尋,則演算法複雜度為o n n 注意到這裡有些元素可能是重複的計算,可以考慮用到動態規劃,可以 利用類似單調遞增 減 棧的東西,儲存之前的值,從而使得演算法複雜度變為o n...
單調棧 最大矩形 單調佇列 滑動視窗
給乙個直方圖,求直方圖中的最大矩形的面積。例如,下面這個中直方圖的高度從左到右分別是2,1,4,5,1,3,3,他們的寬都是1,其中最大的矩形是陰影部分。輸入包含多組資料。每組資料用乙個整數n來表示直方圖中小矩形的個數,你可以假定1 n 100000.然後接下來n個整數h1,hn,滿足 0 hi 1...