求极大子矩阵的两种方法
例1:玉蟾宮
一句話題意:給出一個元素有R和F兩種值的矩陣,求全為F的面積最大的子矩陣的面積。
關于這種求極大子矩陣的問題,比較常用的(本蒟蒻會的)有兩種:
(1)懸線法
/*以下摘自luogu某dalao的解說(略有改動)
用途:
解決給定矩陣中滿足條件的最大子矩陣
做法:
用一條線(橫豎貌似都行)左右移動直到不滿足約束條件或者到達邊界
定義幾個東西:
left[i][j]:代表從(i,j)能到達的最左位置
right[i][j]:代表從(i,j)能到達的最右位置
up[i][j]:代表從(i,j)向上擴展最長長度.
遞推公式:
left[i][j]=max(left[i][j],left[i-1][j])
right[i][j]=min(right[i][j],right[i-1][j])
up[i][j]=up[i-1][j]+1
當前矩陣的面積S=長*寬(高)=(r[i][j]-l[i][j]+1)*(up[i][j])
答案
即所有S中的最大值
至于為什么遞推公式中考慮上一層的情況?
是因為up數組的定義,up數組代表向上擴展最長長度, 所以需要考慮上一層的情況.
摘抄結束qwq*/
玉蟾宮code(1)
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<iostream> using namespace std; const int N=1005; inline int read(){int X=0,w=0;char ch=0;while(!isdigit(ch)) w|=ch=='-',ch=getchar();while(isdigit(ch)) X=(X<<1)+(X<<3)+(ch^48),ch=getchar();return w?-X:X; } inline int readc(){char c=getchar();while(c!='R'&&c!='F')c=getchar();if(c=='F')return 1;return 0; } int n,m,ans1,ans,map[N][N]; int l[N][N],r[N][N],up[N][N]; int main(){register int i,j;n=read(),m=read();for(i=1;i<=n;i++)for(j=1;j<=m;j++){map[i][j]=readc();if(map[i][j])up[i][j]=1,r[i][j]=l[i][j]=j;}for(i=1;i<=n;i++)for(j=2;j<=m;j++)if(map[i][j]==1&&map[i][j-1]==1) l[i][j]=l[i][j-1];for(i=1;i<=n;i++)for(j=m-1;j>0;j--)if(map[i][j]==1&&map[i][j+1]==1)r[i][j]=r[i][j+1];for(i=1;i<=n;i++)for(j=1;j<=m;j++){if(i>1&&map[i][j]==1&&map[i-1][j]==1){r[i][j]=min(r[i][j],r[i-1][j]);l[i][j]=max(l[i][j],l[i-1][j]);up[i][j]=up[i-1][j]+1;}ans=max(ans,(r[i][j]-l[i][j]+1)*up[i][j]);}cout<<ans*3<<endl;return 0; } View Code代碼應該比較好懂就沒注釋了。。。
(2)單調棧
我先假定你會單調棧(如果不會的推薦看這里),我們知道利用單調棧,可以找到數列中任意元素中從左/右遍歷第一個比它小/大的元素的位置,利用這一性質能解決的比較經典的問題有求直方圖中最大矩形的面積。
那么這道題其實也可以轉化成求直方圖中最大矩形的面積,具體做法是:
對于第一步,可以想象處理出了許多條豎直的線段。處理到第i行就以第i行為下界與豎直的線段構成了一個直方圖,然后第2、3步就直接套用求直方圖中最大矩形的面積的做法就行了,s就相當于高度,l和r分別是左右兩邊能控制的極位置。
玉蟾宮code(2)
?
#include<cstdio> #include<cstring> #include<stack> #include<algorithm> #include<iostream> #define lop(i,s,t) for(register int (i)=(s);(i)<=(t);++(i))//我懶到寫宏定義了 。。。 #define nop(i,s,t) for(register int (i)=(s);(i)>=(t);--(i))//應該不算太丑吧。。。 using namespace std; const int N=1005; inline bool getc(){char ch=0;while(ch^'R'&&ch^'F') ch=getchar();return ch=='F'; } bool mp[N][N]; int n,m,ans,s[N][N],l[N][N],r[N][N]; stack<int> st; inline void init(){lop(i,1,n) lop(j,1,m)if(mp[i][j]=getc()) //如果(i,j)合法,那么它可以向上延伸(或者說可以接在上面的豎線之下) s[i][j]=s[i-1][j]+1; } inline void clear(){while(!st.empty()) st.pop();} //沒辦法,stack沒有clear() inline void solve(){lop(i,1,n){clear(); //不要忘了清空棧lop(j,1,m){while(!st.empty()&&s[i][j]<=s[i][st.top()]) st.pop();if(st.empty()) l[i][j]=0;else l[i][j]=st.top();st.push(j);}clear(); nop(j,m,1){while(!st.empty()&&s[i][j]<=s[i][st.top()]) st.pop();if(st.empty()) r[i][j]=m+1; //邊的邊界定為m+1 else r[i][j]=st.top();st.push(j);} }lop(i,1,n)lop(j,1,m)if(s[i][j]) //有高度的才能統計答案,其實也可以不加,反正沒有高度的話乘出來是0不影響答案 ans=max(ans,(r[i][j]-1-l[i][j])*s[i][j]); } int main() {cin>>n>>m;init();solve();cout<<ans*3<<endl;return 0; } View Code?
兩種方法的對比
單調棧和懸線法在理論上時間復雜度差不多,都是O(4*mn)的樣子,但是code(2)常數驚人,luogu上跑1000多ms,而code(1)只用了200多ms,這可能是我用了STL棧的緣故(明明是因為人菜)。。。
?
例2:棋盤制作
一句話題意:給一個01矩陣,定義合法子矩陣為任意相鄰元素的顏色都不相同的子矩陣(即國際象棋棋盤的模樣),求面積最大的正方形合法子矩陣和面積最大的合法子矩陣的面積(當然兩者可以相同)。
可以發現這道題只是判斷合法的條件成了相鄰元素的顏色不能相同而已。
解法(1)
判斷是否合法的時候改用!=就行了
解法(2)
可以發現對于圖上所有的元素一定屬于以下兩種類型:
1.行列奇偶性相同的黑格 || 行列奇偶性不同的白格
2.行列奇偶性相同的白格 || 行列奇偶性不同的黑格
那么在輸入的時候屬于第一種情況的賦1,屬于第二種情況的賦0
這樣就把問題轉換成了求最大同色矩陣(想一想為什么),注意要統計兩遍,分別是同為1和同為0的矩陣,比較方便的做法是第一遍統計為1的矩陣,全部元素取反后再統計一遍為1的矩陣。
這里給出解法(1)的code
#include<cstdio> #include<cstring> #include<cctype> #include<algorithm> #include<iostream> #define lop(i,s,t) for(register int (i)=(s);(i)<=(t);++(i)) #define nop(i,s,t) for(register int (i)=(s);(i)>=(t);--(i)) using namespace std; const int N=2005; inline int read(){int X=0,w=0;char ch=0;while(!isdigit(ch)) w|=ch=='-',ch=getchar();while(isdigit(ch)) X=(X<<1)+(X<<3)+(ch^48),ch=getchar();return w?-X:X; } int n,m,ans1,ans2,a[N][N],l[N][N],r[N][N],up[N][N]; int main() {n=read(),m=read();lop(i,1,n)lop(j,1,m){a[i][j]=read();l[i][j]=r[i][j]=j;up[i][j]=1;}lop(i,1,n)lop(j,2,m)if(a[i][j]!=a[i][j-1])l[i][j]=l[i][j-1];lop(i,1,n)nop(j,m-1,1)if(a[i][j]!=a[i][j+1])r[i][j]=r[i][j+1];lop(i,1,n)lop(j,1,m)if(i>1&&a[i][j]!=a[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]);up[i][j]=up[i-1][j]+1;int a=up[i][j],b=r[i][j]-l[i][j]+1,c=min(a,b);ans1=max(ans1,c*c),ans2=max(ans2,a*b); //順便統計了正方形 }cout<<ans1<<endl<<ans2<<endl; return 0; } View Code?
注意一點
你也許會發現,例1用的懸線法與例2用的懸線法在預處理時略有不同,例1里多了一個判斷:
if(map[i][j]) up[i][j]=1,r[i][j]=l[i][j]=j;
這是因為兩題的題設略有不同,例2里點的合法性是取決于元素間的相對位置,也就是說黑點白點都有可能有貢獻,但例1是只有‘F’點才可能有貢獻,‘R’點是絕對不可能有貢獻的,因此只初始化‘F’點。不信你把那個判斷去掉然后輸入一個全為‘R’的矩陣,程序會輸出非0值。
2018-10-29
轉載于:https://www.cnblogs.com/gosick/p/9867460.html
總結
以上是生活随笔為你收集整理的求极大子矩阵的两种方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: COMP9313 2018s2 Proj
- 下一篇: svn+post-commit实现自动部