NCSTOJ 1134 蓝桥杯最大子阵
Description
给定一个n*m的矩阵A,求A中的一个非空子矩阵,使这个子矩阵中的元素和最大。 其中,A的子矩阵指在A中行和列均连续的一块。
Input
输入的第一行包含两个整数n, m,分别表示矩阵A的行数和列数。(1 ≤ n, m ≤ 500)接下来n行,每行m个整数,表示矩阵A。
Output
输出一行,包含一个整数,表示A中最大的子矩阵中的元素和。
Sample Input
3 3
-1 -4 3
3 4 -1
-5 -2 8
Sample Output
10
前缀和+动态规划
关键是如何把二维的子序和转化为一维
用一个数sum
累加,如果sum>0
说明累加当前的数是会变大或变小的(可能是最优解的一部分),如果sum<0
那么就让sum
等于当前数(因为不可能是最优解,要重新开始),因为累加不肯能比单独现在这个数大
方法:
这里二维转化成一维一个重点就是一维子序和范围可以理解成矩阵中的子矩阵
1 2 3
1 -1 -1 3
2 3 4 -1
3 -5 -2 8
我选择第三列3,-1,8
作为一个一维子序和时,可以得到结果10
,这也是一个子矩阵,因为不管一维二维都是需要连续,二维连续就是子矩阵
范围怎么选定?答案是通过累加,我们不需要知道选的是哪里,只需要知道这一部分和就可以了,如这个例子的最优解是第二列加第三列,计算时只需要把第三列加到第二列,比较答案即可
把二维的子矩阵转化为一维的列的最大子序和,通过枚举所有的列可能性得到答案,列中的所有可能一维子序和帮你枚举了
代码实现:
数据从下标1开始存,初始化为0,这样不用判断范围,也不用担心会影响结果,范围num[n][m]
枚举所有的列,需要两个控制变量,一个是开始范围的i
也就是从第几列开始
i
∈
[
1
,
m
]
i \in [1,m]
i∈[1,m],然后是从i
开始的移动变量j
也就是从i
开始要几列
j
∈
[
i
,
m
]
j\in[i,m]
j∈[i,m],用一个数组dp
保存第k
行的数据,然后累加第j
列,转化为一维子序和求解,更新最大值
#include <vector>
#include <cstring>
#include <iostream>
using namespace std;
static const auto io_sync_off = []() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
return nullptr;
}();
const int maxn = 505;
int num[maxn][maxn];
int dp[maxn];
int main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
cin >> num[i][j];
int ans = num[1][1]; // 初始化
for (int i = 1; i <= m; ++i)
{
memset(dp, 0, sizeof(dp)); // 每次从不同行开始时要清零
for (int j = i; j <= m; ++j)
{
for (int k = 1; k <= n; ++k)
dp[k] += num[k][j]; // 累加第j列
int mcur = dp[1], cur = 0; // 求一维最大子序和
for (int k = 1; k <= n; ++k)
{
if (cur > 0)
cur += dp[k];
else
cur = dp[k];
mcur = max(mcur, cur);
}
ans = max(mcur, ans);
}
}
cout << ans;
return 0;
}
上述代码没有问题,但是求和的时候看着稍微有些累赘,并且要维护一个dp
数组,仔细想想是可以通过前缀和得到某一列的值的,所以在输入数据时将其维护成前缀和数组,这样可以减少一个循环(按说时间因该没变化,但是更慢了wtf???)
要求某一块列的和,那么我们要对行累加(其实不是求列和,而是得到这一列到前面某一列范围内的每行和,就是上一个dp数组,越说越乱,手动模拟一下就好了)
#include <vector>
#include <iostream>
using namespace std;
static const auto io_sync_off = []() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
return nullptr;
}();
const int maxn = 505;
int dp[maxn][maxn];
int main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
{
cin >> dp[i][j];
dp[i][j] += dp[i][j - 1]; // 求i行前缀和
}
int ans = dp[1][1];
for (int i = 1; i <= m; ++i) // 枚举列
for (int j = i; j <= m; ++j) // 要几列
{
int cursum = 0;
for (int k = 1; k <= n; ++k) // 求j-i列之和组成的一维子序和最优解
{
if (cursum > 0)
cursum += dp[k][j] - dp[k][i - 1];
else
cursum = dp[k][j] - dp[k][i - 1];
ans = max(cursum, ans);
}
}
cout << ans;
return 0;
}