用單調佇列求解最大子矩形

2022-08-05 16:45:20 字數 4270 閱讀 3592

考慮這樣一個問題:$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),然後問題就化歸為了上面的模型。面積最大的子正方形只需要求出所有合法矩形中較小邊最大的那個就行了。

1

//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 }

view code

最大子矩形

一個 n m 的矩陣中有 s 個位置是障礙,問最大的不包含障礙的矩形面積 最大子矩形問題 王知坤 對於這篇 吐槽無力 雖然實現 至少它的思路很對嘛 悲慘經歷 找到一份題解,學學學學學學學。wa了。,然後氣憤的測題解,wa了 每個極大子矩形的每一條邊外側一定有至少一個障礙或與邊界重合,不然將邊向外移即...

奶牛浴場 最大子矩形問題

根據王知昆的 裡說的,解決這種問題通常有兩種方法。這裡我用的第二種方法。 什麼都不懂的,先看 具體的沒啥好說的,根據 裡說的實現以下就是了,...

P1578 奶牛浴場 有障礙點的最大子矩形

這題咕咕了很久終於寫了 qwq 顯然能成為答案的矩形的邊界一定有障礙點或者與大矩形邊界重合。 細節見 及註釋 include include...