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 块砖,求得分最多能有多少。

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


正 解 部 分 \color{red}{正解部分}

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

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

于是从右往左按列处理,

F [ i , j , k ] F[i, j, k] F[i,j,k] 表示 i i i N N N 列, 第 i i i 列敲了 k k k 个砖块, 总共敲了 j j j 个砖块,

F [ i , j , k ] = max ⁡ { F [ i + 1 , p , k − j ] + s u m [ i , j ] }          ( p   ≥   k − 1 ) F[i, j, k] = \max\{F[i+1, p, k-j] + sum[i, j]\}\ \ \ \ \ \ \ \ (p\ \geq \ k-1) F[i,j,k]=max{F[i+1,p,kj]+sum[i,j]}        (p  k1) .

A n s = max ⁡ ( F [ 1 , i , M ] ) Ans = \max(F[1, i, M]) Ans=max(F[1,i,M])

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


实 现 部 分 \color{red}{实现部分}

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

20 p t s → 100 p t s 的 区 别 . . . 20pts \rightarrow 100pts 的区别... 20pts100pts...

#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\n", Ans);
        return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值