TYVJ 2218 最大矩阵

最近模拟赛略频繁,导致博客有点断片,自己的计划有点停滞不前,以此来督促一下自己。


题目中说两个矩阵不可以相交,那么这两个矩阵肯定就可以被一条线在原来的矩阵中分开,当然可能是横线也可能是竖线。所以我们可以枚举这条分割线,最多枚举200次,这样枚举的前提就是我们已经求出了分割线两边的大矩阵中的和最大的子矩阵。

那么就是,给出分割线i,以竖直的为例,我们要求的是(1,1)-(n,i)这个大矩阵中的和最大的子矩阵,这里可以用DP的思想。我们设sum[i][0]表示总矩阵第i列至左端这个矩阵中最大子矩阵和,sum[i][1]表示第i列至最右端的最大和。pre[i][j][k]表示以(j,i)-(k,i)为最右边一条边的矩阵中和的最大值(画一画就很形象了,原谅我的文字描述很难看)。pre[i][j][k] = max(pre[i-1][j][k], 0) + col[i][k] - col[i][j],其中col[i][j]表示第i列的前j个之和。我们需要枚举的只有i,j,k,也就是O(n^3)。sum[i][0] = max{pre[i][j][k]}(j <= k <= n)。类似地,我们可以求出sum[i][1]。于是有 ans = max{sum[i][0]*sum[i+1][1]}。

类似地,在使分割线横着求一遍sum[i][0]和sum[i][1]即可,就是sum[i][0]表示第i行以上的最大子矩阵和。

当然这还不够,因为可能两个和都是负数,最后相乘得到正数,这就需要我们再作一次上面的工作,仅仅把求pre数组中的max改为min就好。


自己丑陋的代码,四个过程(竖线-正,竖线-负,横线-正,横线-负),复制粘贴了三次。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

int n, m, a[101][101];
long long pre[101][101][101], suf[101][101][101], col[101][101], sum[101][2];
long long ans = -(1<<30), zero = 0;

int main()
{
	scanf("%d %d", &n, &m);
	for(int i = 1; i <= n; i++)
	for(int j = 1; j <= m; j++)
		scanf("%d", &a[i][j]);
	
	// NO.1
	memset(sum, -0x3f, sizeof sum);
	for(int i = 1; i <= m; i++)
	for(int j = 1; j <= n; j++)
		col[i][j] = col[i][j-1]+a[j][i];
	
	for(int i = 1; i <= m; i++)
	for(int j = 1; j <= n; j++)
	for(int k = j; k <= n; k++){
		pre[i][j][k] = max(pre[i-1][j][k], zero)+col[i][k]-col[i][j-1];
		sum[i][0] = max(sum[i][0], pre[i][j][k]);
	}
	for(int i = 1; i <= m; i++) sum[i][0] = max(sum[i][0], sum[i-1][0]);
	
	for(int i = m; i; i--)
	for(int j = 1; j <= n; j++)
	for(int k = j; k <= n; k++){
		suf[i][j][k] = max(suf[i+1][j][k], zero)+col[i][k]-col[i][j-1];
		sum[i][1] = max(sum[i][1], suf[i][j][k]);
	}
		
	for(int i = 1; i < m; i++)
		ans = max(ans,sum[i][0]*sum[i+1][1]);
	
	// NO.2
	memset(suf, 0, sizeof suf);
	memset(pre, 0, sizeof pre);
	memset(sum, 0x3f, sizeof sum);
	
	for(int i = 1; i <= m; i++)
	for(int j = 1; j <= n; j++)
	for(int k = j; k <= n; k++){
		pre[i][j][k] = min(pre[i-1][j][k], zero)+col[i][k]-col[i][j-1];
		sum[i][0] = min(sum[i][0], pre[i][j][k]);
	}
	
	for(int i = m; i; i--)
	for(int j = 1; j <= n; j++)
	for(int k = j; k <= n; k++){
		suf[i][j][k] = min(suf[i+1][j][k], zero)+col[i][k]-col[i][j-1];
		sum[i][1] = min(sum[i][1], suf[i][j][k]);
	}
	for(int i = 1; i <= m; i++) sum[i][0] = min(sum[i][0], sum[i-1][0]);
		
	for(int i = 1; i < m; i++)
		ans = max(ans, sum[i][0]*sum[i+1][1]);
	
	// NO.3
	memset(col, 0, sizeof col);
	memset(suf, 0, sizeof suf);
	memset(pre, 0, sizeof pre);
	memset(sum, -0x3f, sizeof sum);
	for(int i = 1; i <= n; i++)
	for(int j = 1; j <= m; j++)
		col[i][j] = col[i][j-1]+a[i][j];
	
	for(int i = 1; i <= n; i++)
	for(int j = 1; j <= m; j++)
	for(int k = j; k <= m; k++){
		pre[i][j][k] = max(pre[i-1][j][k], zero)+col[i][k]-col[i][j-1];
		sum[i][0] = max(sum[i][0], pre[i][j][k]);
	}
	
	for(int i = n; i; i--)
	for(int j = 1; j <= m; j++)
	for(int k = j; k <= m; k++){
		suf[i][j][k] = max(suf[i+1][j][k], zero)+col[i][k]-col[i][j-1];
		sum[i][1] = max(sum[i][1], suf[i][j][k]);
	}
	for(int i = 1; i <= n; i++) sum[i][0] = max(sum[i][0], sum[i-1][0]);
		
	for(int i = 1; i < n; i++)
		ans = max(ans, sum[i][0]*sum[i+1][1]);
	
	// NO.4
	memset(suf, 0, sizeof suf);
	memset(pre, 0, sizeof pre);
	memset(sum, 0x3f, sizeof sum);
	
	for(int i = 1; i <= n; i++)
	for(int j = 1; j <= m; j++)
	for(int k = j; k <= m; k++){
		pre[i][j][k] = min(pre[i-1][j][k], zero)+col[i][k]-col[i][j-1];
		sum[i][0] = min(sum[i][0], pre[i][j][k]);
	}
	for(int i = 1; i <= n; i++) sum[i][0] = min(sum[i][0], sum[i-1][0]);
	
	for(int i = n; i; i--)
	for(int j = 1; j <= m; j++)
	for(int k = j; k <= m; k++){
		suf[i][j][k] = min(suf[i+1][j][k], zero)+col[i][k]-col[i][j-1];
		sum[i][1] = min(sum[i][1], suf[i][j][k]);
	}
		
	for(int i = 1; i < n; i++)
		ans = max(ans, sum[i][0]*sum[i+1][1]);
	
	printf("%lld", ans);
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值