题目
分析
一眼看上去就是一道DP!想了很久,码出来一个很复杂的做法,而且还错了……
注意到m<=2,可以利用这个性质:
当m=1时,问题变为求多个子区间使得和最大。这就比较好想了,令f[t][i]表示选到了第i个位置,已经选了t个区间的最大值。得到:
f[t][i]=max{f[t][i],f[t−1][j]+sum[j,i]},j∈[1,i];
f
[
t
]
[
i
]
=
m
a
x
{
f
[
t
]
[
i
]
,
f
[
t
−
1
]
[
j
]
+
s
u
m
[
j
,
i
]
}
,
j
∈
[
1
,
i
]
;
特别的,如果前t-1个矩阵都只有一个元素,那么可以不必枚举额外的j,直接将前面的数加过来:
f[t][t]=sum[0,t];
f
[
t
]
[
t
]
=
s
u
m
[
0
,
t
]
;
当m=2时,我们再分类讨论:
对于第i行的某一列j,它的答案可能来自于:
- 该列前若干行s的答案加上[s,i]这几行第j列的值;
- 另一列j’前若干行s的答案加上[s,i]这几行第j’列的值;
- 如果可以两列同步转移,答案就是这几行两列的值。
设f[t][i][j]表示选了t个区间,其中第0列选到了i,第1列选到了j时的最大值。稍加思考,可得到:
多看几遍转移方程自然理解此题,自然地用上前缀和优化。时间复杂度 O(n3k)。 O ( n 3 k ) 。
代码
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int maxn=102;
int a[maxn][3],sum[maxn][3];
int f[12][maxn][maxn];
int n,m,k,ans;
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++)
for(int j=0;j<m;j++)
scanf("%d",&a[i][j]),sum[i][j]=sum[i-1][j]+a[i][j];
if(m==1)
{
for(int i=1;i<=n;i++)
for(int t=1;t<=k;t++)
{
if(i==t)
{
f[t][i][0]=sum[i][0];
continue;
}
f[t][i][0]=f[t][i-1][0];
for(int j=t-1;j<i;j++)
f[t][i][0]=max(f[t][i][0],f[t-1][j][0]+sum[i][0]-sum[j][0]);
}
printf("%d",f[k][n][0]);
return 0;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
for(int t=1;t<=k;t++)
{
f[t][i][j]=max(f[t][i-1][j],f[t][i][j-1]);
for(int l=0;l<i;l++) f[t][i][j]=max(f[t][i][j],f[t-1][l][j]+sum[i][0]-sum[l][0]);
for(int l=0;l<j;l++) f[t][i][j]=max(f[t][i][j],f[t-1][i][l]+sum[j][1]-sum[l][1]);
if(i==j)
for(int l=0;l<i;l++)
f[t][i][j]=max(f[t][i][j],f[t-1][l][l]+sum[i][0]-sum[l][0]+sum[j][1]-sum[l][1]);
}
printf("%d",f[k][n][n]);
return 0;
}
/*附赠便于观察的样例格式
3 2 2
1 -3
2 3
-2 3
*/