P1437 [HNOI2004]敲砖块 一道十分难想的DP题
题意:题意很简单,有n层方块,每层逐次减一块,每一块有一定权值;
想要敲掉某块,就要先敲掉他的基甸,然后给我们一个操作数m,
询问m个操作最多能得多少分;
思路:在最开始得思考中,直接考虑每一层逐一dp下来,会发现,每一层所包含得数与相邻得数都会有重复(除第一层外)
这句话什么意思呢?比如,想要敲掉第三层第一个数,就得先敲1 2 第二个数就得敲 2 3
那么有重复得数,我们就不好操作,无法列出转移方程
无从下手,看了别人博客后,发现是将其360°左右翻转,然后就能列出式子来
举个例子
2 2 3 4 翻转过后就是 4 3 2 2 8 2 7 7 2 8 2 3 3 2 49 49
那么 现在得情况是 第二列得8需要敲掉2 2 第二列得2 需要敲掉3 2 第二列得7需要敲掉3 4
我们先来观察一下这个图,假如我们想要在第三列敲两个数,那么只能敲前两个,因为第三个需要以前面得为基准
然后,想要敲这两个数,前面一列(也就是3 7这一列)就必须要有一个数或者一个数以上。
为什么呢?因为第三列第二个数需要以第二列第一个数为奠基
所以我们想要选择某一列的k个数的时候,肯定为前k个,于是我们可以预处理出前缀和,这是第一步
然后,定义一个dp【i】【j】【k】数组
表述前i列敲掉j块,并且在第i列敲掉k块时的最大权值
于是,我们按列从左往右依次遍历,for(int i=1;i<=n;i++)
接下来就是遍历在当前列要枚举的总砖数 for(int j=0;j<=min(m,(i+1)*i/2);j++)
再遍历当前列要选几块 for(int k=0;k<=min(i,j);k++)
再遍历上一列选几块 for(int l=max(0,k-1);l<=i-1;l++)
然后就能得出所有最优情况
我们最后再来遍历找值即可
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int maxn=55; 4 const int inf=0x3f3f3f3f; 5 int n,m,f[maxn][maxn]; 6 int dp[maxn][maxn*maxn][maxn]; 7 int main() 8 { 9 scanf("%d%d",&n,&m); 10 for(int i=1;i<=n;i++) 11 for(int j=n;j>=i;j--){ 12 scanf("%d",&f[i][j]); 13 f[i][j]+=f[i-1][j]; 14 } 15 // for(int i=1;i<=n;i++){ 16 // for(int j=i;j<=n;j++){ 17 // printf("%d ",f[i][j]); 18 // } 19 // printf("\n"); 20 // } 21 memset(dp,-inf,sizeof(dp)); 22 dp[0][0][0]=0; 23 for(int i=1;i<=n;i++) 24 for(int j=0;j<=min(m,(i+1)*i/2);j++)//总砖数 25 for(int k=0;k<=min(i,j);k++)//当前列选几块 26 for(int l=max(0,k-1);l<=i-1;l++)//上一列选几块 27 dp[i][j][k]=max(dp[i][j][k],dp[i-1][j-k][l]+f[k][i]); 28 int ans=0; 29 for(int i=1;i<=n;i++) 30 for(int j=0;j<=i;j++) 31 ans=max(ans,dp[i][m][j]); 32 printf("%d\n",ans); 33 return 0; 34 }