原题: https://www.luogu.org/problemnew/show/P2331
题意:
n*m的矩阵,你选出其中k个不重叠子矩阵,使得这个k个子矩阵分值之和最大。子矩阵可以是空矩阵。
解析:
被空矩阵秀到了,是我线代不好还是出题人的问题。。。
这题有个特点,m最大为2,也就是说,选取的可能性为:选左边一条,右边一条,或是两格宽的一条。
m==1的部分就不说了
解法一:O( K)
自己想的,看了数据范围也没多想提起代码就上了。
dp[i][j][k]
表示左边已经到i,右边到j,已经选择k个矩阵的最大值。
转移方程:
- 选左边:
- 选右边:
- 选两边:
这个best
数组是什么意思呢?
因为
这个状态通过
只能转移到
这个状态,但是我下一个矩阵选择可能是从
来的。
简言之,我在更新一个状态后,需要是二维偏序大于这个状态的状态也可以使用这个更新。
如果每次更新都往后一个一个更新太浪费时间了,所以记录best[l][r]
,表示从l到r这个区间选择一个子区间的最大值。
这样后序状态也可以使用这个更新了。
不要问我为什么不把“往后更新”换成“从前面更新”,我敲的时候也没有想到这茬啊,best这东西也是临时想的。。。
AC代码:
#include<bits/stdc++.h>
using namespace std;
#define debug(i) printf("# %d\n",i)
int a[109][3];
int s[3][109];
int best[3][109][109];
int dp[109][109][11];
const int inf=0xc0c0c0c0;
void init(int n,int m){
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
s[i][j]=s[i][j-1]+a[j][i];
}
for(int j=1;j<=n;j++){
best[i][j][j]=a[j][i];
}
for(int l=2;l<=n;l++){
for(int j=1;j+l-1<=n;j++){
best[i][j][j+l-1]=max(best[i][j][j+l-2],max(best[i][j+1][j+l-1],s[i][j+l-1]-s[i][j-1]));
}
}
}
if(m==2){
for(int i=1;i<=n;i++)best[0][i][i]=a[i][1]+a[i][2];
for(int l=2;l<=n;l++){
for(int j=1;j+l-1<=n;j++){
best[0][j][j+l-1]=max(best[0][j][j+l-2],max(best[0][j+1][j+l-1],s[2][j+l-1]-s[2][j-1]+s[1][j+l-1]-s[1][j-1]));
}
}
}
}
int main(){
int n,m,k;
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%d",a[i]+j);
}
}
init(n,m);
//*********
if(m==1){
int ans=0;
for(int i=0;i<n;i++){
for(int j=0;j<k;j++){
for(int l=1;;l++){
if(i+l>n)break;
dp[0][i+l][j+1]=max(dp[0][i+l][j+1],dp[0][i][j]+best[1][i+1][i+l]);
}
}
}
for(int i=1;i<=n;i++){
for(int t=0;t<=k;t++)
ans=max(ans,dp[0][i][t]);
}
printf("%d\n",ans);
return 0;
}
//*********
for(int i=0;i<=n;i++){
for(int j=0;j<=n;j++){
for(int l=1;;l++){
if(i+l>n)break;
for(int t=0;t<k;t++){
dp[i+l][j][t+1]=max(dp[i+l][j][t+1],dp[i][j][t]+best[1][i+1][i+l]);
}
}
for(int l=1;;l++){
if(j+l>n)break;
for(int t=0;t<k;t++){
dp[i][j+l][t+1]=max(dp[i][j+l][t+1],dp[i][j][t]+best[2][j+1][j+l]);
}
}
for(int l=1;;l++){
int to=max(i,j);
if(to+l>n)break;
for(int t=0;t<k;t++){
dp[to+l][to+l][t+1]=max(dp[to+l][to+l][t+1],dp[i][j][t]+best[0][to+1][to+l]);
}
}
}
}
int ans=0;
for(int i=0;i<=n;i++){
for(int j=0;j<=n;j++){
for(int t=0;t<=k;t++)
ans=max(ans,dp[i][j][t]);
}
}
printf("%d\n",ans);
return 0;
}
解法二:O( K)
对于答案状态分析,每一行只有5种状态:00,10,01,1|1,11。最后两种:1|1代表两个格子在不同的两条矩阵种,11代表这行在一个两格宽的矩阵里。
想到这里,转移方程就再简单不过了。
dp[i][j][k]
表示到第
行,用
个矩阵,
行状态为
的最值
- ~
#include<bits/stdc++.h>
using namespace std;
int a[109][2];
int dp[109][11][7];
const int INF=0x3f3f3f3f;
int main(){
int n,m,k;
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",a[i]+j);
// 不存在状态一定没有存在状态优
memset(dp,0xc0,sizeof dp);
for(int i=0;i<=n;i++)dp[i][0][0]=0;
if(m==1){
int ans=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=k;j++){
dp[i][j][0]=max(dp[i-1][j][0],dp[i-1][j][1]);
dp[i][j][1]=max(dp[i-1][j][1],dp[i-1][j-1][0])+a[i][1];
ans=max(ans,max(dp[i][j][0],dp[i][j][1]));
}
}
return 0*printf("%d\n",ans);
}
int ans=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=k;j++){
int add;
dp[i][j][0]=max(dp[i-1][j][0],max(dp[i-1][j][1],max(dp[i-1][j][2],max(dp[i-1][j][3],dp[i-1][j][4]))));
add=a[i][1];
dp[i][j][1]=max(dp[i-1][j][1],dp[i-1][j][3]);
dp[i][j][1]=max(dp[i][j][1],max(dp[i-1][j-1][0],max(dp[i-1][j-1][2],dp[i-1][j-1][4])));
dp[i][j][1]+=add;
add=a[i][2];
dp[i][j][2]=max(dp[i-1][j][2],dp[i-1][j][3]);
dp[i][j][2]=max(dp[i][j][2],max(dp[i-1][j-1][0],max(dp[i-1][j-1][1],dp[i-1][j-1][4])));
dp[i][j][2]+=add;
add=a[i][1]+a[i][2];
dp[i][j][3]=dp[i-1][j][3];
dp[i][j][3]=max(dp[i][j][3],max(dp[i-1][j-1][1],dp[i-1][j-1][2]));
if(j>1)dp[i][j][3]=max(dp[i][j][3],max(dp[i-1][j-2][0],dp[i-1][j-2][4]));
dp[i][j][3]+=add;
dp[i][j][4]=dp[i-1][j][4];
dp[i][j][4]=max(dp[i][j][4],max(dp[i-1][j-1][0],max(dp[i-1][j-1][1],max(dp[i-1][j-1][2],dp[i-1][j-1][3]))));
dp[i][j][4]+=add;
}
}
for(int i=0;i<=k;i++)
for(int j=0;j<=4;j++)
ans=max(ans,dp[n][i][j]);
return 0*printf("%d\n",ans);
}