NEFU OJ 2350 小兰参加蓝桥杯 (计算两个不相交子矩阵和)

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 行到第 iiinnn 行到第 iii 的最大值(相当于在第 iii 行和第 i+1i+1i+1 行进行分割)。
这样最终结果即为上下两部分的和。

具体思路:

  • 首先用第一层循环设置 iii 确定分割线位置 ,然后计算上下两部分最大值,存储在 udp[i]udp[i]udp[i]ddp[i]ddp[i]ddp[i] 中。
  • 中间两层循环分别设置 lllrrr ,表示选取该行的 [l,r][l,r][lr] 区间。
  • 然后判断该行的 [l,r][l,r][lr] 区间是否应该接在前一行的 [l,r][l,r][lr] 区间上:
    1、若上一行(或多行)的 [l,r][l,r][lr] 区间和 >0>0>0 ,说明这一行接到上面可以使子矩阵的和增大,此时应该加上上一行。
    2、若上一行(或多行)的 [l,r][l,r][lr] 区间和 <0<0<0 ,则说明这一行应该作为第一行重新进行计算。
  • 然后更新 udp[i]udp[i]udp[i] ,记录加入 iii 行的 [l,r][l,r][lr] 区间后,上半部分最大值( 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][lr] 区间和的方法就很好想了(前缀和)

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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值