[HNOI2004]敲砖块(dp)

[HNOI2004]敲砖块(luogu)

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][ki]) (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;
}

 

 

猜你喜欢

转载自www.cnblogs.com/hsez-cyx/p/12315649.html