[ZJOI2007] 棋盘制作(单调栈 / DP悬线法)
problem
洛谷鏈接
solution1-單調(diào)棧
很容易想到,預(yù)處理出每個(gè)點(diǎn)向上最大能延伸的長度,然后對(duì)每個(gè)點(diǎn)求一個(gè)矩陣面積。
然后思考優(yōu)化,不難想到每次對(duì)一行進(jìn)行求解。
每一行的所有列一起構(gòu)成了一個(gè)直方圖。
直方圖直接經(jīng)典笛卡爾樹。笛卡爾樹性質(zhì)是什么?是單調(diào)棧!
直接對(duì)每一行都建一個(gè)單調(diào)棧,遍歷列維護(hù)遞增棧,彈出時(shí)計(jì)算答案。正方形將兩邊取個(gè) min?\minmin 再比就行了。
這個(gè)做法很簡單,但是直觀上是 O(n3)O(n^3)O(n3) 的,所以很多人不敢打。
但實(shí)際上跑很快,因?yàn)槊總€(gè)點(diǎn)只會(huì)被做一次。一次單調(diào)棧是做連續(xù)一段。
時(shí)間復(fù)雜度應(yīng)該是 O(n2)O(n^2)O(n2) 的。
#include <bits/stdc++.h> using namespace std; #define maxn 2005 int n, m, top, ans1, ans2; int h[maxn], s[maxn]; int c[maxn][maxn];int main() {scanf( "%d %d", &n, &m );for( int i = 1;i <= n;i ++ )for( int j = 1;j <= m;j ++ )scanf( "%d", &c[i][j] );for( int i = 1;i <= n;i ++ ) { //枚舉每一行for( int j = 1;j <= m;j ++ ) //預(yù)處理該行上每一列最長能向上延伸的長度if( i > 1 and c[i][j] ^ c[i - 1][j] ) ++ h[j];else h[j] = 1;for( int j = 1, k;j <= m;j = k ) {s[0] = j - 1, s[top = 1] = j;for( k = j + 1;k <= m and c[i][k] ^ c[i][k - 1];k ++ ) {while( top and h[s[top]] > h[k] ) {ans1 = max( ans1, min(h[s[top]], k - s[top - 1] - 1) * min(h[s[top]], k - s[top - 1] - 1) );ans2 = max( ans2, h[s[top]] * (k - s[top - 1] - 1) );top --;}s[++ top] = k;}while( top ) {ans1 = max( ans1, min(h[s[top]], k - s[top - 1] - 1) * min(h[s[top]], k - s[top - 1] - 1) );ans2 = max( ans2, h[s[top]] * (k - s[top - 1] - 1) );top --;}}}printf( "%d\n%d\n", ans1, ans2 );return 0; }solution2-DP懸線法
最大子矩陣很熟悉啊!是 DPDPDP 入門必講的經(jīng)典例題。
對(duì)每一個(gè)位置 (i,j)(i,j)(i,j) 預(yù)處理出其向上/向左/向右分別能延伸到多遠(yuǎn)。
然后詢問每一個(gè)位置 (i,j)(i,j)(i,j) 被包含的最大子矩陣,如果和上一行是合法的就要結(jié)合上一行的左右限制。
具體而言,左邊限制取較大值,右邊限制取較小值,本質(zhì)是取最靠近 (i,j)(i,j)(i,j) 的左右兩邊限制。
取完后,矩形長即 r(i,j)?l(i,j)+1r(i,j)-l(i,j)+1r(i,j)?l(i,j)+1,寬即 h(i,j)h(i,j)h(i,j)。就可以求了。
這里需要注意的是,不能在預(yù)處理的時(shí)候,就考慮上一行的左右限制。
//這么寫就是錯(cuò)的 for( int i = 1;i <= n;i ++ )for( int j = 1;j <= m;j ++ )if( j > 1 and c[i][j] ^ c[i][j - 1] )if( h[i][j] > 1 ) l[i][j] = max( l[i - 1][j], l[i][j - 1] );else l[i][j] = l[i][j - 1];else l[i][j] = j; for( int i = 1;i <= m;i ++ ) r[0][i] = m; for( int i = 1;i <= n;i ++ )for( int j = m;j;j -- )if( j < m and c[i][j] ^ c[i][j + 1] )if( h[i][j] > 1 ) r[i][j] = min( r[i - 1][j], r[i][j + 1] );else r[i][j] = r[i][j + 1];else r[i][j] = j;你過早的刻畫了左右邊界,就會(huì)出現(xiàn)上面這種。雖然對(duì)于紫紅色點(diǎn)而言確實(shí)是這個(gè)矩形。
但對(duì)于后面的黃色點(diǎn)而言,其遞推的左邊界也被限制在了黑線處,就算不到紅色矩形了。
#include <bits/stdc++.h> using namespace std; #define maxn 2005 int n, m; int c[maxn][maxn], h[maxn][maxn], l[maxn][maxn], r[maxn][maxn];int main() {scanf( "%d %d", &n, &m );for( int i = 1;i <= n;i ++ )for( int j = 1;j <= m;j ++ )scanf( "%d", &c[i][j] );for( int i = 1;i <= n;i ++ )for( int j = 1;j <= m;j ++ )if( i > 1 and c[i][j] ^ c[i - 1][j] ) h[i][j] = h[i - 1][j] + 1;else h[i][j] = 1;for( int i = 1;i <= n;i ++ )for( int j = 1;j <= m;j ++ )if( j > 1 and c[i][j] ^ c[i][j - 1] ) l[i][j] = l[i][j - 1];else l[i][j] = j;for( int i = 1;i <= n;i ++ )for( int j = m;j >= 1;j -- )if( j < m and c[i][j] ^ c[i][j + 1] ) r[i][j] = r[i][j + 1];else r[i][j] = j;int ans1 = 0, ans2 = 0;for( int i = 1;i <= n;i ++ )for( int j = 1;j <= m;j ++ ) {if( i > 1 and c[i][j] ^ c[i - 1][j] ) {l[i][j] = max( l[i][j], l[i - 1][j] );r[i][j] = min( r[i][j], r[i - 1][j] );}ans1 = max( ans1, min( r[i][j] - l[i][j] + 1, h[i][j] ) * min( r[i][j] - l[i][j] + 1, h[i][j] ) );ans2 = max( ans2, ( r[i][j] - l[i][j] + 1 ) * h[i][j] );}printf( "%d\n%d\n", ans1, ans2 );return 0; }總結(jié)
以上是生活随笔為你收集整理的[ZJOI2007] 棋盘制作(单调栈 / DP悬线法)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 三星 T9 移动固态硬盘双 11 促销:
- 下一篇: [ZJOI2007] 时态同步(拓扑序)