P1437 [HNOI2004]敲砖块 [dp]

敲砖块

在一个凹槽中放置了 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 块砖,求得分最多能有多少。

1n501mn(n+1)/21≤n≤50,1≤m≤n*(n+1)/2


color{red}{正解部分}

若按行处理, 需要考虑这一行的每个元素是否可选, 但是 是否可选 这个问题不好处理 .

但若按列处理, 可以发现从上往下敲的砖块是连续的,
若从右往左处理, 又可以发现题目中的限制条件可以比较方便地处理 .

于是从右往左按列处理,

F[i,j,k]F[i, j, k] 表示 iiNN 列, 第 ii 列敲了 kk 个砖块, 总共敲了 jj 个砖块,

F[i,j,k]=max{F[i+1,p,kj]+sum[i,j]}        (p  k1)F[i, j, k] = max{F[i+1, p, k-j] + sum[i, j]} (p geq k-1) .

Ans=max(F[1,i,M])Ans = max(F[1, i, M])

时间复杂度 O(N4)O(N^4) .


color{red}{实现部分}

  1. 状态转移的来源一定要限制 !!!
  2. 注意 MM 的范围, F[][][]F[][][] 的第二维要开到 N2N^2 级别 !!!

20pts100pts...20pts ightarrow 100pts 的区别...

#include<bits/stdc++.h>
#define reg register

const int maxn = 55;

int N;
int M;
int Ans;
int A[maxn][maxn];
int sum[maxn][maxn];
int F[maxn][maxn*(maxn+1)/2][maxn];

int main(){
        scanf("%d%d", &N, &M);
        for(reg int i = 1; i <= N; i ++)
                for(reg int j = 1; j <= N-i+1; j ++) scanf("%d", &A[i][j]);
        for(reg int i = 1; i <= N; i ++)
                for(reg int j = 1; j <= N-i+1; j ++) sum[i][j] = sum[i][j-1] + A[j][i];
        for(reg int i = N; i >= 1; i --) // cur_pos
                for(reg int j = 1; j <= M; j ++) //tot
                        for(reg int k = 0; k <= j; k ++) //cur
                                for(reg int p = std::max(0, k-1); p <= std::min(N-i, j-k); p ++) //last
                                        F[i][j][k] = std::max(F[i][j][k], F[i+1][j-k][p] + sum[i][k]);
        for(reg int i = 0; i <= N; i ++) Ans = std::max(Ans, F[1][M][i]);
        printf("%d
", Ans);
        return 0;
}

原文地址:https://www.cnblogs.com/zbr162/p/11822522.html