最大子阵
描述
给定一个n*m的矩阵A,求A中的一个非空子矩阵,使这个子矩阵中的元素和最大。 其中,A的子矩阵指在A中行和列均连续的一块。
Input
第一行一个整数T,代表样例总数。
输入的第一行包含两个整数n, m,分别表示矩阵A的行数和列数。
接下来n行,每行m个整数,表示矩阵A。
Output
输出一行,包含一个整数,表示A中最大的子矩阵中的元素和。
Example Input
1 3 3 -1 -4 3 3 4 -1 -5 -2 8
Example Output
10
样例说明
取最后一列,和为10。
数据规模和约定
对于50%的数据,1<=n, m<=50; 对于100%的数据,1<=n, m<=500,A中每个元素的绝对值不超过5000。
对于这种题目一眼就看出来是最大子序列问题的变种,但是问题在于,这样的题目怎么做呢?两方向都dp吗?这样好像不太现实(反正我没成功),后来参考了网上的答案做出来了。
解法是采用通过遍历所有子矩阵的初末位置,来控制列的范围(控制初末列位置),累加每列的行之和,采用dp的方法找到该子阵(该子阵由于控制列的范围,所以行的初末固定,列的大小与原数组一样)中行数均为起始到末尾的最大子子阵(即行数为起始到末尾的最大子阵),由于每种行数的情况下均会在遍历时处理,因此当遍历完全部的数组时,最大值即为矩阵的最大子矩阵。
本题的可行性在于,所有的初始,末尾列的位置都能在两个for循环遍历到,那么就可以得到每个确定初始,末尾列位置的小范围(设为range A)内每行的个数确定的(每行的个数为从初始到末尾列的位置的个数)最大矩阵A,由于任何一种初末列数确定的情况都能遍历到,所以会在某一时刻找到原来的小范围(range A)内每行的个数小于该确定范围的最大子矩阵B(该矩阵相当于初始,末尾列的位置为B矩阵的初始末尾列的位置每行的个数确定的最大矩阵A')所以能找到在确定了初末列的位置的子矩阵中最大的子矩阵,当范围为初始到末尾时,能找到这个数组的最大子阵,也就是正确答案。
AC代码:
#include <stdio.h>
#include <string.h>
int a[505][505], dp[505], row[505];
int main(void)
{
int t, n, m;
scanf("%d", &t);
while(t--)
{
int ans = -2e9;
scanf("%d %d", &n, &m);
for(int i = 0; i < n; i++)
for(int j = 0; j < m; j++)
scanf("%d", &a[i][j]);
for(int i = 0; i < m; i++)//确定初始列的位置
{
memset(row, 0, sizeof(row));
for(int p = i; p < m; p++)//确定末尾列的位置
{
dp[0] = row[0] += a[0][p];//计算第0行确定的范围的数的累加(当范围扩大时只需要加上新范围,但起始列变动时得重置
if(dp[0] > ans)//如果第0行之和大于ans
ans = dp[0];
for(int j = 1; j < n; j++)//采用dp方法找到行数确定的最大子矩阵
{
row[j] += a[j][p];
if(dp[j - 1] < 0)//小于0,加上下一行所有元素和肯定没有下一行所有元素和大
dp[j] = row[j];
else
dp[j] = dp[j - 1] + row[j];
if(dp[j] > ans)
ans = dp[j];
}
}
}
printf("%d\n", ans);
}
return 0;
}