Time Limit:1000ms
Memory Limit:65535K
Description
小蓝在机房中教授蓝桥杯。
机房共有 N 排电脑,每排有M 台。
由于位置、风水等等原因,每一台电脑都有自己的效率值。
位于第 i 排的第 j 台电脑的效率值为V[i][j] 。
现在小蓝想同时为两批不同水平的选手讲课,这就使得这两批人的位置不能有相交。
为了使同一水平层次的人之间能有更好的交流,
小蓝会预留出来自机房中某一个子矩阵的所有座位提供给他们。
显然小蓝需要为每一批人至少预留一个座位。
因此,实际授课时,小蓝就需要预留出两个子矩阵的座位。
小蓝让同学们尽可能地高效听课,
因此他想知道所有预留出的座位的效率值之和最大是多少。
Input
第一行包含两个整数 N 和M ,表示机房电脑排数和每排电脑数。
接下来有 N 行,每行M 个整数,
第i 行第j 个数字表示
位于第i 排的第j 台电脑的效率值V[i][j] 。
Output
输出一个整数,表示两个子矩阵中电脑效率值之和最大值。
Sample Input:
3 2
1 -1
2 -2
3 -3
Sample Output:
6
Hint
Solution
题意比较明确:在原矩阵中找出两个最大的不相交子矩阵,使他们的和最大。
首先观察两个不相交子矩阵的分布情况(图片为几种情况的示例):
可以发现两个子矩阵的位置关系始终是 水平 的或者是 垂直 的。
也就是说总可以找到一条水平或垂直的分割线将两个子矩阵分开。
这样就可以分别从水平方向和垂直方向尝试分割。
首先考虑在 水平方向 进行分割:
分别从上到下和从下到上记录 第 1 行到第 iii 行 和 第 nnn 行到第 iii 行 的最大值(相当于在第 iii 行和第 i+1i+1i+1 行进行分割)。
这样最终结果即为上下两部分的和。
具体思路:
- 首先用第一层循环设置 iii 确定分割线位置 ,然后计算上下两部分最大值,存储在 udp[i]udp[i]udp[i] 和 ddp[i]ddp[i]ddp[i] 中。
- 中间两层循环分别设置 lll 和 rrr ,表示选取该行的 [l,r][l,r][l,r] 区间。
- 然后判断该行的 [l,r][l,r][l,r] 区间是否应该接在前一行的 [l,r][l,r][l,r] 区间上:
1、若上一行(或多行)的 [l,r][l,r][l,r] 区间和 >0>0>0 ,说明这一行接到上面可以使子矩阵的和增大,此时应该加上上一行。
2、若上一行(或多行)的 [l,r][l,r][l,r] 区间和 <0<0<0 ,则说明这一行应该作为第一行重新进行计算。 - 然后更新 udp[i]udp[i]udp[i] ,记录加入 iii 行的 [l,r][l,r][l,r] 区间后,上半部分最大值( ddp[i]ddp[i]ddp[i] 为下半部分最大值)。
- 中间循环后更新 udp[i]udp[i]udp[i] 为第 iii 行及以前分割出子矩阵的最大值。
- 最后找出 udp[i]udp[i]udp[i] 与 ddp[i+1]ddp[i+1]ddp[i+1] 相加即为水平分割时两个子矩阵和的最大值。
垂直方向分割与水平方向同理(就不写了 )。
对于循环中计算 [l,r][l,r][l,r] 区间和的方法就很好想了(前缀和)
Code
注意:NEFU OJ 会卡时间,需要手动开 O2 防止 TLE。
#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, m;
ll a[501][501], hsum[501][501], vsum[501][501];
ll udp[501], ddp[501], last[2][501][501];
ll getAns()
{
ll curLine, ans = LLONG_MIN;
for (int i = 1; i <= n; i++) //从第1行(或n行)到第i行
{
udp[i] = ddp[n - i + 1] = LLONG_MIN;
for (int l = 1; l <= m; l++) //列左侧
{
for (int r = l; r <= m; r++) //列右侧
{
curLine = hsum[i][r] - hsum[i][l - 1];
last[0][l][r] = max(last[0][l][r], 0LL) + curLine; //从上到下
udp[i] = max(udp[i], last[0][l][r]);
curLine = hsum[n - i + 1][r] - hsum[n - i + 1][l - 1];
last[1][l][r] = max(last[1][l][r], 0LL) + curLine; //从下到上
ddp[n - i + 1] = max(ddp[n - i + 1], last[1][l][r]);
}
}
if(i!=1) udp[i] = max(udp[i],udp[i-1]);
if(i!=n) ddp[i] = max(ddp[i],ddp[i+1]);
}
for (int i = 1; i < n; i++) ans = max(ans, udp[i] + ddp[i + 1]);
memset(last, 0, sizeof(last));
for (int i = 1; i <= m; i++) //从第1列(或m列)到第i列
{
udp[i] = ddp[m - i + 1] = LLONG_MIN;
for (int l = 1; l <= n; l++) //行上侧
{
for (int r = l; r <= n; r++) //行下侧
{
curLine = vsum[r][i] - vsum[l - 1][i];
last[0][l][r] = max(last[0][l][r], 0LL) + curLine;
udp[i] = max(udp[i], last[0][l][r]);
curLine = vsum[r][m - i + 1] - vsum[l - 1][m - i + 1];
last[1][l][r] = max(last[1][l][r], 0LL) + curLine;
ddp[m - i + 1] = max(ddp[m - i + 1], last[1][l][r]);
}
}
if(i!=1) udp[i] = max(udp[i],udp[i-1]);
if(i!=m) ddp[i] = max(ddp[i],ddp[i+1]);
}
for (int i = 1; i < m; i++) ans = max(ans, udp[i] + ddp[i + 1]);
return ans;
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
scanf("%lld", &a[i][j]);
hsum[i][j] = hsum[i][j - 1] + a[i][j];
vsum[i][j] = vsum[i - 1][j] + a[i][j];
}
}
printf("%lld", getAns());
return 0;
}