Description
在一个凹槽中放置了 n 层砖块、最上面的一层有 n 块砖,从上到下每层依次减少一块砖。
每块砖都有一个分值,敲掉这块砖就能得到相应的分值,如下图所示。
14 15 4 3 23 33 33 76 2 2 13 11 22 23 31
如果你想敲掉第 i 层的第 j 块砖的话,若 i=1,你可以直接敲掉它;若 i>1,则你必须先敲掉第 i-1 层的第 j 和第 j+1 块砖。
你现在可以敲掉最多 m 块砖,求得分最多能有多少。
输入格式
输入文件的第一行为两个正整数 n 和 m;接下来 n 行,描述这 n 层砖块上的分值 a[i][j],满足 0≤a[i][j]≤100。
对于 100% 的数据,满足 1≤n≤50,1≤m≤n*(n+1)/2;
输出格式
输出文件仅一行为一个正整数,表示被敲掉砖块的最大价值总和。
Solution
容易看出这道题用dp做,但是是否敲掉上层的砖会对下层产生影响,不满足使用dp三前提中的“无后效性”。
上下不行,不妨换个角度,看看左右行不行
分析性质
敲掉(i , j)要先敲掉(i-1 , j)(i-1 , j+1),层层递进,发现敲掉(i , j)要敲掉整个以(i , j)为顶点的三角形
于是可以推出我们最后敲掉的木块构成的轮廓是锯齿状的,如下图中黑色的线
设计状态
那么我们就可以设计 dp 的状态
f[ i ][ j ][ k ]表示以 ( i , j ) 为轮廓上最左边的突起(如上图中绿色的点),一共敲了k个木块的最大得分。
为什么要用轮廓上最左边的突起表示状态?
因为只有它能概括轮廓左边的状态,
比如用上图中紫色的点则还可以看做紫色的线表示的轮廓,表示状态不唯一。
状态转移
由于我们是从右到左转移,所以阶段为 j 。
要求 f[ i ][ j ][ k ] ,可以将问题分治。
考虑从左往右第二个突起是否在 以列数为j+1的所有点构成的直线(一条与整个等腰三角形右边平行的直线)上
如果在:这个突起的行数必然 >=i,因为要囊括( i -1, j+1 )为顶点的三角形以保证可以敲(i,j)这个木块
如果不在:由于阶段为 j ,我们要把它用 f[ ~ ][ j+1 ][ ~ ]表示出来。
还是因为要囊括( i -1, j+1 )为顶点的三角形,所以只能等价于f[i-1][j+1][ k-j]
于是有了决策:
f[i][j][k]=$\sum\limits_{l=1}^{i}$a[l][j]+max(f[i′][j+1][k−i]) (max(0,i-1)<=i'<=n-j)
发现 i 一定时有大量重复计算,可以优化一下
Code
#include <cstdio> #include <cstdlib> #include <algorithm> using namespace std; const int N=51,M=1251; int mf[N][N][M],f[N][N][M],a[N][N],n,m,ans; int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) for(int j=1;j<=n-i+1;j++) scanf("%d",&a[i][j]),a[i][j]+=a[i-1][j]; for(int j=n;j;j--) for(int i=n-j+1;i>=0;i--) for(int k=(i+1)*i/2;k<=m;k++) { f[i][j][k]=mf[max(i-1,0)][j+1][k-i]+a[i][j]; mf[i][j][k]=max(mf[i+1][j][k],f[i][j][k]); ans=max(ans,f[i][j][k]); } printf("%d\n",ans); return 0; }