BZOJ1084&&洛谷P2331 [SCOI2005]最大子矩阵

DP+思维

思路

这道题的切入点是 m m m,发现 m m m只有两种取值,那么我们就可以尝试对 m m m分类讨论

m=1

发现在 m = 1 m=1 m=1时就是在一个一维序列上做k个最大子段和,我们定义 f [ i ] [ j ] f[i][j] f[i][j]表示处理到第 i i i位,共 j j j个矩阵的最大和,咋转移?
1. 1. 1. 假设这一位不选,那就是 f [ i ] [ j ] = f [ i − 1 ] [ j ] f[i][j]=f[i-1][j] f[i][j]=f[i1][j]
2. 2. 2.否则枚举上一个矩形结束位置 k k k,那么
f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ k ] [ j − 1 ] + s [ k ] − s [ j ] f[i][j]=max(f[i][j],f[k][j-1]+s[k]-s[j] f[i][j]=max(f[i][j],f[k][j1]+s[k]s[j]
s是前缀和
最后输出 f [ n ] [ e ] f[n][e] f[n][e]就好了

m=2

在二维下,我们类比一维,但是因为一个矩形可以占据一列 ,也可以占据两列,所以我们定义 g [ i ] [ j ] [ k ] g[i][j][k] g[i][j][k]表示第一列到第 i i i行,第二列到第 j j j行,共k个矩形的最大和,如何转移?我们枚举 i i i j j j
1. 1. 1. i , j i,j ij都不选, g [ i ] [ j ] [ k ] = m a x ( g [ i − 1 ] [ j ] [ k ] , g [ i ] [ j − 1 ] [ k ] ) g[i][j][k]=max(g[i-1][j][k],g[i][j-1][k]) g[i][j][k]=max(g[i1][j][k],g[i][j1][k])
2. 2. 2.考虑在第一列上转移,枚举 p p p,可以得到
g [ i ] [ j ] [ k ] = m a x ( g [ i ] [ j ] [ k ] , g [ p ] [ j ] [ k − 1 ] + s 1 [ i ] − s 1 [ p ] ) g[i][j][k]=max(g[i][j][k],g[p][j][k-1]+s1[i]-s1[p]) g[i][j][k]=max(g[i][j][k],g[p][j][k1]+s1[i]s1[p])
3. 3. 3.考虑在第二列上转移,枚举 p p p,可以得到
g [ i ] [ j ] [ k ] = m a x ( g [ i ] [ j ] [ k ] , g [ i ] [ p ] [ k − 1 ] + s 2 [ j ] − s 2 [ p ] ) g[i][j][k]=max(g[i][j][k],g[i][p][k-1]+s2[j]-s2[p]) g[i][j][k]=max(g[i][j][k],g[i][p][k1]+s2[j]s2[p])
4. 4. 4.考虑两列一起转移,这样的情况存在,当且仅当 i = j i=j i=j时,这样才能构造出一个新矩形,然后我们枚举一个p,可以得到
g [ i ] [ j ] [ k ] = m a x ( g [ i ] [ j ] [ k ] , g [ p ] [ p ] [ k − 1 ] + s 1 [ i ] − s 1 [ p ] + s 2 [ j ] − s 2 [ p ] ) g[i][j][k]=max(g[i][j][k],g[p][p][k-1]+s1[i]-s1[p]+s2[j]-s2[p]) g[i][j][k]=max(g[i][j][k],g[p][p][k1]+s1[i]s1[p]+s2[j]s2[p])
s 1 s1 s1是第一列前缀和, s 2 s2 s2是第二列前缀和
最后输出 g [ n ] [ n ] [ e ] g[n][n][e] g[n][n][e]

代码

//By AcerMo
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int M=105;
int n,m,e,a[M][3];
int s1[M],s2[M],f[M][M],g[M][M][M];
signed main()
{
	scanf("%d%d%d",&n,&m,&e);
	for (int i=1;i<=n;i++)
	for (int k=1;k<=m;k++)
	scanf("%d",&a[i][k]);
	if (m==1)
	{
		for (int i=1;i<=n;i++) 
		s1[i]=s1[i-1]+a[i][1];
		for (int i=1;i<=e;i++)
		for (int k=1;k<=n;k++)
		{
			f[k][i]=f[k-1][i];
			for (int j=0;j<k;j++)
			f[k][i]=max(f[k][i],f[j][i-1]+s1[k]-s1[j]);
		}
		return printf("%d",f[n][e]),0;
	}
	for (int i=1;i<=n;i++) 
	s1[i]=s1[i-1]+a[i][1],
	s2[i]=s2[i-1]+a[i][2];
	for (int i=1;i<=e;i++)
	for (int k=1;k<=n;k++)
	for (int j=1;j<=n;j++)
	{
		g[k][j][i]=max(g[k-1][j][i],g[k][j-1][i]);
		for (int p=0;p<k;p++) 
		g[k][j][i]=max(g[k][j][i],g[p][j][i-1]+s1[k]-s1[p]);
		for (int p=0;p<j;p++)
		g[k][j][i]=max(g[k][j][i],g[k][p][i-1]+s2[j]-s2[p]);
		if (k==j) 
		for (int p=0;p<k;p++)
		g[k][j][i]=max(g[k][j][i],g[p][p][i-1]+s1[k]+s2[j]-s1[p]-s2[p]);
	}
	printf("%d",g[n][n][e]);
	return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值