最大子矩阵和 = 前缀和 + 最大子段和
简单来说这道题就是求一个 \(N \times M\) 的矩阵的最大子矩阵和。
(因为求的是黑色石板与白色石板的数量差,所以代表白色石板的“0”可以看作 -1,这样就将问题转化为了求最大子矩阵和)
思路:
首先,这个子矩阵可以是任意大小的,而且起始点也可以在任何地方,所以,要把最大子矩阵找出来,我们要考虑多种情况。
假定原始矩阵的行数为 \(M\),那么对于子矩阵,它的行数可以是 1 到 \(M\) 的任何一个数,而且,对于一个 \(K\) 行(\(K \le M\))的子矩阵,它的第一行可以是原始矩阵的第 1 行到 \(M - K + 1\) 的任意一行。
例子:
1 -1 1 1 -1
-1 1 1 1 1
1 1 1 1 -1
1 -1 1 -1 1
对于样例二的矩阵,如果子矩阵的行数是 2,那么它可以是下面几个矩阵的子矩阵:
1 -1 1 1 -1
-1 1 1 1 1
或者
-1 1 1 1 1
1 1 1 1 -1
或者
1 1 1 1 -1
1 -1 1 -1 1
在每一种情况里(我们这里有三种),我们还要找出一个最大的子矩阵,当然,这只是一种情况的最大子矩阵(局部最大),不一定是全局最大。
但是,如果我们知道每一种情况的最大,要找出全局最大,那就小菜一碟儿了。
在讲在一个特殊情况下求最大子矩阵之前,先讲一个事实:
假设这个最大子矩阵的维数是一维,要找出最大子矩阵, 原理与求“最大子段和问题”是一样的。最大子段和问题的递推公式是 :
\(b[j]\) 指的是从1开始到 \(j\) 的最大子段和。
例子:
假设原始矩阵为:[1 ,-1 ,1 ,1 ,-1], 那么 \(b[]\) = {1,0,1,2,1} , 那么最大子段和为 2,如果找最大子矩阵的话,那么这个子矩阵是 [1,-1,1,1] (或者[1,1]) 。
求最大子段和的代码如下:
int b[1000]={};
for(int k=1;k<=n;k++){
b[k]=max(a[k],b[k-1]+a[k]);
ans=max(ans,b[k]);
}
printf("%d",ans);
但是,原始矩阵可以是二维的。假设原始矩阵是一个 \(3 \cdot n\) 的矩阵,那么它的子矩阵可以是 \(1 \cdot k\), \(2 \cdot k\), \(3 \cdot k\)(\(1 \leq k \leq n\))。
如果是 \(1 \cdot k\),这里有3种情况:子矩阵在第一行,子矩阵在第二行,子矩阵在第三行。
如果是 \(2 \cdot k\),这里有两种情况,子矩阵在第一、二行,子矩阵在第二、三行。
如果是 \(3 \cdot k\),只有一种情况。
为了能够找出最大的子矩阵,我们需要考虑所有的情况。
假设这个子矩阵是 \(2 \cdot k\), 也就是说它只有两行,要找出最大子矩阵,我们要从左到右不断的遍历才能找出在这种情况下的最大子矩阵。如果我们把这两行上下相加,情况就和求“最大子段和问题” 又是一样的了。
为了找出在原始矩阵里的最大子矩阵,我们要遍历所有的子矩阵的可能情况,也就是说,我们要考虑这个子矩阵有可能只有 1 行,2 行……到 \(n\) 行。而在每一种情况下,我们都要把它所对应的矩阵部分上下相加才求最大子矩阵(局部)。
所以我边输入,边预处理出了每一列的前缀和:
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>(s+1);
for(int j=1;j<=m;j++){
dp[j][i]+=dp[j][i-1];
if(s[j]-'0') dp[j][i]++;
else dp[j][i]--;
}
}
//dp[j][i] 表示 第j列 1到第i行的前缀和
之后枚举矩阵的起点行和终点行,通过最大子段和的方法求最大子矩阵:
for(int i=1;i<=n;i++){ //枚举起点行
for(int j=i;j<=n;j++){ //枚举终点行
int b[1000]={};
for(int k=1;k<=m;k++){ //前缀和+最大子段和
b[k]=max(dp[k][j]-dp[k][i-1],b[k-1]+dp[k][j]-dp[k][i-1]);
ans=max(ans,b[k]);
}
}
}
完整代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,ans;
char s[1000];
int dp[1000][1000];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>(s+1);
for(int j=1;j<=m;j++){
dp[j][i]+=dp[j][i-1];
if(s[j]-'0') dp[j][i]++;
else dp[j][i]--;
}
}
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j++){
int b[1000]={};
for(int k=1;k<=m;k++){
b[k]=max(dp[k][j]-dp[k][i-1],b[k-1]+dp[k][j]-dp[k][i-1]);
ans=max(ans,b[k]);
}
}
}
cout<<ans;
return 0;
}