[ZJOI2007] 棋盘制作(单调栈 / DP悬线法)
problem
洛谷鏈接
solution1-單調棧
很容易想到,預處理出每個點向上最大能延伸的長度,然后對每個點求一個矩陣面積。
然后思考優化,不難想到每次對一行進行求解。
每一行的所有列一起構成了一個直方圖。
直方圖直接經典笛卡爾樹。笛卡爾樹性質是什么?是單調棧!
直接對每一行都建一個單調棧,遍歷列維護遞增棧,彈出時計算答案。正方形將兩邊取個 min?\minmin 再比就行了。
這個做法很簡單,但是直觀上是 O(n3)O(n^3)O(n3) 的,所以很多人不敢打。
但實際上跑很快,因為每個點只會被做一次。一次單調棧是做連續一段。
時間復雜度應該是 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 ++ ) //預處理該行上每一列最長能向上延伸的長度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 入門必講的經典例題。
對每一個位置 (i,j)(i,j)(i,j) 預處理出其向上/向左/向右分別能延伸到多遠。
然后詢問每一個位置 (i,j)(i,j)(i,j) 被包含的最大子矩陣,如果和上一行是合法的就要結合上一行的左右限制。
具體而言,左邊限制取較大值,右邊限制取較小值,本質是取最靠近 (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)。就可以求了。
這里需要注意的是,不能在預處理的時候,就考慮上一行的左右限制。
//這么寫就是錯的 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;你過早的刻畫了左右邊界,就會出現上面這種。雖然對于紫紅色點而言確實是這個矩形。
但對于后面的黃色點而言,其遞推的左邊界也被限制在了黑線處,就算不到紅色矩形了。
#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; }總結
以上是生活随笔為你收集整理的[ZJOI2007] 棋盘制作(单调栈 / DP悬线法)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 三星 T9 移动固态硬盘双 11 促销:
- 下一篇: 漫步者 G1S 雷霆版耳机上架:支持三模