前言
嗯,虽然题面出锅,意思不明,但这是出题人的问题,怎么能怪题呢(雾
排除了这一切,它的 idea 最终还是好的。
题目描述(修正)
你有一个 n ∗ m n * m n∗m 的矩阵,每个位置上都有一个整数 v v v。
你要在矩阵中选择一个 M \texttt{M} M 形。
M \texttt{M} M 形是这么定义的:
它可以被拆分为左上(1),右上(2),左下(3),中下(4),右下(5)共 5 5 5 个横向平行的矩形,并保证:
- (1)的右边与(2)的左边有公共部分。
- (1)的左边和(3)的左边无公共边但共线,(1)的下边真包含(3)的上边。
- (1)的右边和(4)的右边无公共边但共线,(1)的下边真包含(4)的上边。
- (2)的右边和(5)的右边无公共边但共线,(2)的下边真包含(5)的上边。
- (2)的左边和(4)的右边不存在包含关系。
- (3),(4),(5)如果将竖直边延长为直线,则两两不能有重合。
- 每个矩形的大小至少为 1*1,不能退化成线或点。
以下就是一个标准的 M \texttt{M} M 形。
求出总和最大的一个
M
\texttt{M}
M 形,并输出总和。
1 ≤ n , m ≤ 100 1 \leq n,m \leq 100 1≤n,m≤100, ∣ v ∣ ≤ 100 |v| \leq 100 ∣v∣≤100
题解1 O ( n 4 ) O(n^4) O(n4)
第一感:哦天哪,这题是什么神仙……一点头绪都没有
直接统计 M \texttt{M} M 形显然是不可行的,考虑拆分或者取补来统计
盲目地拆分与取补没有任何作用。我们要把问题约化成更可解的问题。
思索一番后,发现可以提取中间段,分割两种情况统计。
具体地,将(4)与(1)中被(4)的竖直平行线截取的部分单独提出枚举,问题就约化成如何找到最大的 “L” 形。
怎么统计 “L” 形呢?
首先我们把 “L” 形拆成两段统计,分为横段和竖段。
可以通过如下步骤统计:
- 确定竖段在左上角确定,且不能过右边的一条竖线时的最大值。时间复杂度 O ( n 4 ) O(n^4) O(n4)
- 利用 1,得出横段在右上角确定且宽确定时 “L” 形的最大值。时间复杂度 O ( n 4 ) O(n^4) O(n4)
然后统计反向的 “L” 形:
类似正向 “L” 形,可以通过如下步骤统计:
- 确定竖段在右上角确定,且不能过左边的一条竖线时的最大值。时间复杂度 O ( n 4 ) O(n^4) O(n4)
- 利用 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 n≤100,所以我们要卡常。。。
题解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{dpp−1,i−1,a,⋯} 或 d p p , i − 1 , a , b dp_{p,i-1,a,b} dpp,i−1,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;
}