P2331 [SCOI2005]最大子矩阵
DP
注意到m<=2
那就很好搞了
又发现n<=100,k<=10
随便暴力DP都可以了
然后注意一下 可以选空矩阵
状态直接设:f[ i ][ j ][ k ] 表示 第一行 已经选到第 i 个数, 第二行 已经选到第 j 个数,一共选了 k 个矩阵
那么 f[ i ][ j ][ k ]=max( f[ l ][ j ][ k-1 ]+ Σ(num1[ l ] ~ num1[ i ]) ( 0<=l<=i ) ,
f[ i ][ l ][ k-1 ]+ Σ(num2[ l ] ~ num2[ i ]) ( 0<=l<=j ) )
对于宽为2的矩形就特判一下就好了 :
if(i == j) f[ i ][ j ][ k ]=max( f[ i ][ j ][ k ],f[ l ][ l ][ k-1 ]+Σ(num1[ l ] ~ num1[ i ] + num2[ l ] ~ num2[ i ]) (0<=l<=i))
顺便写个前缀和 快速求区间值不难吧...
完全可过
#include<iostream> #include<algorithm> #include<cstdio> #include<cmath> #include<cstring> using namespace std; int n,m,K; int sum[107][2],f[107][107][17]; int main() { int a,b; scanf("%d%d%d",&n,&m,&K); if(m==1) { for(int i=1;i<=n;i++) { scanf("%d",&a); sum[i][0]=sum[i-1][0]+a; } for(int k=1;k<=K;k++) for(int i=1;i<=n;i++) { f[i][0][k]=f[i-1][0][k]; for(int j=0;j<=i;j++) f[i][0][k]=max(f[i][0][k],f[j][0][k-1]+sum[i][0]-sum[j][0]); } printf("%d",f[n][0][K]); } else { for(int i=1;i<=n;i++) { scanf("%d%d",&a,&b); sum[i][0]=sum[i-1][0]+a; sum[i][1]=sum[i-1][1]+b; } for(int k=1;k<=K;k++) { for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { f[i][j][k]=max(f[i-1][j][k],f[i][j-1][k]); for(int l=0;l<=i;l++) f[i][j][k]=max(f[i][j][k],f[l][j][k-1]+sum[i][0]-sum[l][0]); for(int l=0;l<=j;l++) f[i][j][k]=max(f[i][j][k],f[i][l][k-1]+sum[j][1]-sum[l][1]); if(i==j) for(int l=0;l<=i;l++) f[i][j][k]=max(f[i][j][k],f[l][l][k-1] + (sum[i][0]-sum[l][0]) + (sum[i][1]-sum[l][1]) ); } } } printf("%d",f[n][n][K]); } return 0; }
但是其实有一种更快的方法
这里才是重点
对于前面的方程,每次更新都要把上一步所有的值都找一遍
主要是因为我们不知道这次的矩形要选多长
但是其实如果上一个数被选了,且这个数也要选
那就可以把这两个数合成一个矩形,还可以省一个矩形
实在不行 多出来的矩形也可以选空矩形(可能会选到只剩负数)
所有就有一个比较贪心的思想:
对于每一行数,我们只要注意上一行数的状态
那就不用注意更前面的数了
那么上一步有什么状态呢
注意m最大就是2
所以状态也不多:
设 f[ i ][ j ][ k ]表示选到了第 i 行,已经选了 j 个矩形 , 状态为 k 的最大值
那么当 k=0 就表示 这一行不选
k=1 就表示 这一行只选左边一个数
k=2 就表示 这一行只选右边一个数
k=3 就表示 这一行选两个数,但是不属于同一个矩形
k=4 就表示 这一行选两个数,而且是属于同一个矩形
有了状态,转移也就不难了,慢慢推吧...
#include<iostream> #include<algorithm> #include<cmath> #include<cstdio> #include<cstring> using namespace std; const int INF=1e9+7; int n,m,K; int f[107][17][5]; int main() { int a,b; scanf("%d%d%d",&n,&m,&K); memset(f,128,sizeof(f)); for(int i=0;i<=n;i++) for(int k=0;k<=K;k++) f[i][k][0]=0; if(m==1) { for(int i=1;i<=n;i++) { scanf("%d",&a);//可以边读入边转移 for(int k=1;k<=K;k++) { f[i][k][0]=max(f[i-1][k][0],f[i-1][k][1]); f[i][k][1]=max(f[i-1][k-1][0],f[i-1][k][1])+a; //如果只有一列,那么只有选或不选,转移显然... } } printf("%d",max(f[n][K][0],f[n][K][1]));//对最后一行两个状态取个max,因为不知道哪个状态更好 } else { for(int i=1;i<=n;i++) { scanf("%d%d",&a,&b);//边读入边转移,挺方便的 for(int k=1;k<=K;k++) { f[i][k][0]=max( max(f[i-1][k][0],f[i-1][k][1]) , max(f[i-1][k][2],f[i-1][k][3]) ); f[i][k][0]=max(f[i][k][0],f[i-1][k][4]); //对于不选,那最大值就是上一行所有状态的最大值 f[i][k][1]=max( max(f[i-1][k-1][0],f[i-1][k][1]) , max(f[i-1][k-1][2],f[i-1][k][3]) ) + a; f[i][k][1]=max(f[i][k][1],f[i-1][k-1][4]+a); //注意上一行 k 的变化 //如果上一行只选左边选了,或者左右两边都选(不是同一个矩形),那就可以直接合并,所以矩形数不用变; //如果上一行没选,或者只选右边,或者上一行是一个大矩形,那就要从 k-1 的状态转移过来,因为当前矩形数要比上一行多一 f[i][k][2]=max( max(f[i-1][k-1][0],f[i-1][k-1][1]) , max(f[i-1][k][2],f[i-1][k][3]) ) + b; f[i][k][2]=max(f[i][k][2],f[i-1][k-1][4]+b); //跟状态为1的转移差不多... f[i][k][3]=max( f[i-1][k-1][1] , max(f[i-1][k-1][2],f[i-1][k][3]) ) + a+b; //如果上一行有选一边,那矩形数就只要比上一行多1,如果上一行也是状态3那矩形数就不用变 if(k>1) f[i][k][3]=max(f[i-1][k-2][0]+a+b , max(f[i][k][3],f[i-1][k-2][4]+a+b)); //注意如果是从状态 0 或 状态 4 转移过来,没办法合并,那么矩形数就要比上一行多2,要判一下 k 的值是否大于 1 f[i][k][4]=max( max(f[i-1][k-1][0],f[i-1][k-1][1]) , max(f[i-1][k-1][2],f[i-1][k-1][3]) ) + a+b; f[i][k][4]=max(f[i][k][4],f[i-1][k][4]+a+b); //除非上一行也是状态 4 ,不然都没法合并,所以其他状态都要从 k-1 转移过来 } } int ans=-INF; for(int k=0;k<5;k++) ans=max(ans,f[n][K][k]);//对最后一行取个max,因为不知道最后一行在状态什么下最优 printf("%d",ans); } return 0; }