记一道比较奇特的好题

前言

嗯,虽然题面出锅,意思不明,但这是出题人的问题,怎么能怪题呢(雾

排除了这一切,它的 idea 最终还是好的。

题目描述(修正)

你有一个 n ∗ m n * m nm 的矩阵,每个位置上都有一个整数 v v v

你要在矩阵中选择一个 M \texttt{M} M 形。

M \texttt{M} M 形是这么定义的:

它可以被拆分为左上(1),右上(2),左下(3),中下(4),右下(5)共 5 5 5 个横向平行的矩形,并保证:

  1. (1)的右边与(2)的左边有公共部分。
  2. (1)的左边和(3)的左边无公共边但共线,(1)的下边真包含(3)的上边。
  3. (1)的右边和(4)的右边无公共边但共线,(1)的下边真包含(4)的上边。
  4. (2)的右边和(5)的右边无公共边但共线,(2)的下边真包含(5)的上边。
  5. (2)的左边和(4)的右边不存在包含关系。
  6. (3),(4),(5)如果将竖直边延长为直线,则两两不能有重合。
  7. 每个矩形的大小至少为 1*1,不能退化成线或点。

以下就是一个标准的 M \texttt{M} M 形。

在这里插入图片描述
求出总和最大的一个 M \texttt{M} M 形,并输出总和。

1 ≤ n , m ≤ 100 1 \leq n,m \leq 100 1n,m100 ∣ v ∣ ≤ 100 |v| \leq 100 v100

题解1 O ( n 4 ) O(n^4) O(n4)

第一感:哦天哪,这题是什么神仙……一点头绪都没有

直接统计 M \texttt{M} M 形显然是不可行的,考虑拆分或者取补来统计

盲目地拆分与取补没有任何作用。我们要把问题约化成更可解的问题。

思索一番后,发现可以提取中间段,分割两种情况统计。

具体地,将(4)与(1)中被(4)的竖直平行线截取的部分单独提出枚举,问题就约化成如何找到最大的 “L” 形。

怎么统计 “L” 形呢?

首先我们把 “L” 形拆成两段统计,分为横段和竖段。

在这里插入图片描述
可以通过如下步骤统计:

  1. 确定竖段在左上角确定,且不能过右边的一条竖线时的最大值。时间复杂度 O ( n 4 ) O(n^4) O(n4)
  2. 利用 1,得出横段在右上角确定且宽确定时 “L” 形的最大值。时间复杂度 O ( n 4 ) O(n^4) O(n4)

然后统计反向的 “L” 形:

在这里插入图片描述
类似正向 “L” 形,可以通过如下步骤统计:

  1. 确定竖段在右上角确定,且不能过左边的一条竖线时的最大值。时间复杂度 O ( n 4 ) O(n^4) O(n4)
  2. 利用 1,得出横段在左上角确定且宽确定时 “L” 形的最大值。时间复杂度 O ( n 4 ) O(n^4) O(n4)

最后枚举中间 “I” 的左上角,宽和长,基于以上处理数值就能得出答案。时间复杂度 O ( n 4 ) O(n^4) O(n4)

总复杂度: O ( n 4 ) O(n^4) O(n4)

但是我们看到 n ≤ 100 n\leq 100 n100,所以我们要卡常。。。

题解2 O ( n 3 ) O(n^3) O(n3)

然而这题有非常 general 的做法。甚至有(伪)加强版。

详见 【NOI2013】书法家

依据那题的想法,我们考虑 dp 的状态怎么设。

M \texttt{M} M 形竖直砍成 5 段,如图。

在这里插入图片描述

d p p , i , a , b dp_{p,i,a,b} dpp,i,a,b 代表当前处理第 p p p 块,右界是 i i i,上界是 a a a,下界是 b b b 时的最大值。

那么每个 d p p , i , a , b dp_{p,i,a,b} dpp,i,a,b 都能由 max ⁡ { d p p − 1 , i − 1 , a , ⋯ } \max \{ dp_{p-1,i-1,a, \cdots} \} max{dpp1,i1,a,} d p p , i − 1 , a , b dp_{p,i-1,a,b} dpp,i1,a,b 转移。(其中 ⋯ \cdots 部分,当 p p p 为奇时为 < b < b <b,为偶时为 > b > b >b

所以边 dp 边处理前后缀 max 数组即可。

代码:

#include <iostream>
#include <cstdio>
using namespace std;
const int N=105,INF=0x7fffffff;
int val[N][N],sum[N][N];
int dp[6][N][N][N],max1[6][N][N][N],max2[6][N][N][N];
int main()
{
	int n,m,i,j,k,l;
	scanf("%d%d",&n,&m);
	if(n<2||m<5)
	{
		printf("No 'M'");
		return 0;
	}
	for(i=1;i<=n;i++)
	{
		for(j=1;j<=m;j++)
		{
			scanf("%d",&val[i][j]);
			sum[i][j]=sum[i-1][j]+val[i][j];
		}
	}
	for(i=1;i<=5;i++)
		for(j=0;j<=m+1;j++)
			for(k=0;k<=n+1;k++)
				for(l=0;l<=n+1;l++)
					dp[i][j][k][l]=max1[i][j][k][l]=max2[i][j][k][l]=-INF;
	for(i=1;i<=5;i++)
	{
		for(j=1;j<=m;j++)
		{
			for(k=1;k<=n;k++)
			{
				for(l=k;l<=n;l++)
				{
					if(i&1) dp[i][j][k][l]=max1[i-1][j-1][k][l-1];
					else dp[i][j][k][l]=max2[i-1][j-1][k][l+1];
					dp[i][j][k][l]=max(dp[i][j][k][l],dp[i][j-1][k][l]);
					if(dp[i][j][k][l]==-INF) continue;
					dp[i][j][k][l]+=sum[l][j]-sum[k-1][j];
				}
				max1[i][j][k][k-1]=-INF;
				for(l=k;l<=n;l++) max1[i][j][k][l]=max(dp[i][j][k][l],max1[i][j][k][l-1]);
				max2[i][j][k][n+1]=-INF;
				for(l=n;l>=k;l--) max2[i][j][k][l]=max(dp[i][j][k][l],max2[i][j][k][l+1]);
			}
		}
	}
	int ans=-INF;
	for(i=1;i<=m;i++)
		for(j=1;j<=n;j++)
			for(k=j;k<=n;k++)
				ans=max(ans,dp[5][i][j][k]);
	printf("%d",ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值