求解此类问题要找规律,首先看一下维度为1时的该问题。
①当维度为1时。
此问题变为求最大连续子段和。此时可以用动态规划来解决该问题。
动态规划的思路也很简单
步骤 1:令状态 dp[i] 表示以 A[i] 作为末尾的连续序列的最大和(这里是说 A[i] 必须作为连续序列的末尾)。
步骤 2:做如下考虑:因为 dp[i] 要求是必须以 A[i] 结尾的连续序列,那么只有两种情况:
这个最大和的连续序列只有一个元素,即以 A[i] 开始,以 A[i] 结尾。
这个最大和的连续序列有多个元素,即从前面某处 A[p] 开始 (p<i),一直到 A[i] 结尾。
对第一种情况,最大和就是 A[i] 本身。
对第二种情况,最大和是 dp[i-1]+A[i]。
于是得到状态转移方程:
dp[i] = max{A[i], dp[i-1]+A[i]}
那么求最大连续子段和的核心代码为:
for (int i=1;i<=n;i++){
dp[i]=max(a[i],a[i]+dp[i-1]);
ans=max(ans,dp[i]);
}
cout<<ans<<endl;
维度为1时非常简单,那么维度为2时该怎么办呢?
②当维度为2时。
问题变为在一个矩阵中,找一个他的子矩阵,该子矩阵是所有子矩阵中和最大的。首先我们想到暴力枚举,那么复杂度便为O(N * N * M * M * Sum),其中若不优化,要求Sum也是很困难的。
那么首先让我们使用前缀来优化求解Sum的时间复杂度。
求其中子矩阵的和,肯定需要多次询问。那么最好的解决办法就是前缀和。什么是前缀和?设a[i][j]为第j列前i行的和。我们有了求一维度最大连续子段和的经验,我们可以将维度拆开,枚举其中一维,然后用DP扫另一维,这样可以让复杂度降到O(N * M * M )
假设枚举x维度,核心代码
ll sqsum(int x1,int x2,int s){
return a[x2][s] - a[x1][s];
}
int main(){
for(int j=1;j<=k;j++)
for(int i=1;i<=c;i++)
a[i][j] += a[i-1][j];
for(int i=1;i<=k;i++)
for(int j=i;j<=k;j++){
memset(dp,0,sizeof(dp));
for(int v =1 ;v<=g;v++){
dp[v] = max(dp[v-1],0) + sqsum(i,j,v);
anss=max(anss,dp[v]);
}
}
cout<<anss<<endl;
}
有了前两维,那么第三维就很容易了。
③当维度为3时。
有了前面的经验,当维度为3时,枚举两维中的所有矩阵,对第三维进行扫描DP即可。
这时候前缀和a[i][j][k]为在K维度下的a[0][j][k]~a[i][j][k]的和加上a[i][0][k] ~a[i][j][k] 的和。既然这样,那么再求以(x1,y1)为左上角坐标和(x2,y2)为右下角坐标的子矩阵的所有元素和。就变得简单了。这时候的和为 a[x2][y2] - a[x1-1][y2] - a[x2][y1-1] + a[x1-1][y1-1](此时a为前缀和的a)同样这样可以将最后一维度的O(n^2)的复杂度降为O(n),枚举的复杂度仍没有变,为O( N * N * M * M),总体复杂度为O(N^5)
核心代码
ll sqsum(int x1,int y1,int x2,int y2,int s){
return a[x2][y2][s] + a[x1-1][y1-1][s] - a[x1-1][y2][s] - a[x2][y1-1][s] ;
}
int main(){
for(int l=1;l<=g;l++)
for(int i=1;i<=c;i++)
for(int j=1;j<=k;j++)
a[i][j][l] += a[i-1][j][l] + a[i][j-1][l] - a[i-1][j-1][l];
for(int i=1;i<=c;i++)
for(int j=1;j<=k;j++)
for(int p = 1;p <= i;p++)
for(int q = 1;q <= j;q++){
memset(dp,0,sizeof(dp));
for(int v =1 ;v<=g;v++){
dp[v] = max(dp[v-1],0) + sqsum(p,q,i,j,v);
anss=max(anss,dp[v]);
}
}
cout<<anss<<endl;
}