Shooting Bricks HDU - 7110(动态规划)

题意

  1. 给我们一个 n*m 的矩阵,每个矩阵格子上有一个砖块🧱,每个格子有两个属性:(分数,是否获得子弹),也就是说如果我们用子弹把这个砖块给打掉的话,会获得这个相应格子上的分数,并且这个格子如果奖励子弹的话,我们又会获得一发子弹,这样的话相当于没有消耗子弹。
  2. 刚开始矩阵第 n 层暴露在外面,我们只能打暴露的砖块,如果我们打了某一列的暴露的砖块,那么这个砖块的上面(那一层的)那个砖块就变的暴露了,
  3. 现在我们有 k 发子弹,问我们能获得的最大分数是多少?

思路

  1. 首先这题不能用分组背包!!!

  2. 这一题的特性就是:我们手里先有至少一发子弹,才可以去打砖块,当我们去打带奖励子弹的砖块的时候,我们手里必须也要有子弹,先消耗一颗在获得一起,这与 “打带奖励的砖块不消耗子弹” 是有区别的。

  3. 这个时候我们可以考虑一种巧妙的思路:我们把最后一颗子弹 和 前 k-1 颗子弹分离开,我们在 dp 的时候暴力讨论最后一颗子弹是用在那一列。

  4. 如果我们消耗前 k-1 颗子弹中的某一颗去打某个不带奖励的砖块的话,那么这个不带奖励的砖块的后面的所有带奖励的砖块我们一定都要打了,因为我们有 “最后一颗子弹” 去帮我们打带奖励的砖块,并且打完之后我们的 “最后一颗子弹” 又被奖励了回来。

  5. 我们可以想到前 k-1 课子弹一定是被 “打不带奖励的砖块” 给用掉了,而这些 “被打掉的不带奖励的砖块的后面相邻连续的带奖励的砖块都会” 被 “ 最后一发子弹给打掉”,而 “最后一颗子弹” 又会被奖励回来。

  6. 那么我们考虑 “最后一发子弹” 是什么时候被用掉的呢?当然也是在打某一列 “不带奖励的砖块” 给消耗掉了,但是这个时候与这个不带奖励的砖块的后面及时还有 “带奖励的砖块” 也不能被打掉了(因为最后一颗已经被消耗了)。

  7. 接下来就是特殊考虑 “最后一发子弹” 在那一列被使用的时候的 dp 了,因此我们可以在 dp 数组的基础上多开一维表示我们是否要在这里列的去使用 “最后一发子弹”

  8. 有了上面的思路我们就可以假设出:v [i][x][0~1]

    1. v [i][x][0] 表示对于第 i 列,并且不消耗最后一发,从底部向上打掉第 x 个 “不带奖励的砖块”(注意这时候会消耗k-1 颗中的 x 颗子弹作为费用),和这个砖块后面所有连续的 “带奖励的砖块” 所能获得的最大分数。
    2. v [i][x][1] 表示对于第 i 列,并且消耗最后一发,从底部向上打掉第 x 个 “不带奖励的砖块”, 所能获得的最大分数。
  9. 我们再假设 dp [i][x][0~1]

    1. dp [i][x][0] 表示前 i 列 消耗 x 颗子弹(不含最后一颗子弹),所能得到的最大分数。
    2. dp [i][x][0] 表示前 i 列 消耗 x 颗子弹(包含最后一颗子弹),所能得到的最大分数。
  10. 考虑状态转移:dp [i][x][1] ,因为我们当前考虑到第 i 列了,我们考虑两种情况:

    1. 如果我们第 i 列没有用最后一颗子弹,那么前 i-1 列里面必然要包含 “最后一颗子弹”,因此可以从 dp [i-1][y][1] 转移过来。
    2. 如果我们第 i 列使用了最后一颗子弹,那么前 i-1 里面必然没有使用 “最后徐一颗子弹”,因此可以从 dp [i-1][y][0] 状态转移而来。

注意上面 y 只是一个,只是表述方便我随意想的值,在状态转移里面是有确定的值的

代码

#include <bits/stdc++.h>
using namespace std;
#define db  double
#define ll  long long
#define Pir pair<int, int>
#define fi  first
#define se  second
#define pb  push_back
#define m_p make_pair
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
/*==========ACMer===========*/
const int N = 205;
int n, m, k;
int a[N][N], b[N][N];
int dp[N][N][2], v[N][N][2];


int main()
{
    int T; scanf("%d", &T);
    while (T --)
    {
        char ch;
        scanf("%d %d %d", &n, &m, &k);
        for (int i = 1; i <= n; i ++)
            for (int j = 1; j <= m; j ++)
            {
                scanf("%d %c ", &a[i][j], &ch);
                b[i][j] = (ch == 'Y');
            }

        memset(v, 0, sizeof v);
        for (int j = 1; j <= m; j ++)
        {
            int cnt = 0;
            for (int i = n; i >= 1; i --)
            {
                if (b[i][j]) v[j][cnt][0] += a[i][j];
                else cnt ++, v[j][cnt][0] = v[j][cnt][1] = v[j][cnt - 1][0] + a[i][j];
            }
        }

        memset(dp, 0, sizeof dp);
        for (int i = 1; i <= m; i ++)           //枚举列
        {
            for (int j = 0; j <= k; j ++)       //枚举背包容量
            {
                for (int l = 0; l <= min(n, j); l ++)       //枚举当前列用多少发子弹
                {
                    dp[i][j][0] = max(dp[i][j][0], dp[i - 1][j - l][0] + v[i][l][0]);
                    if (j - l) dp[i][j][1] = max(dp[i][j][1], dp[i - 1][j - l][1] + v[i][l][0]);
                    if (l)     dp[i][j][1] = max(dp[i][j][1], dp[i - 1][j - l][0] + v[i][l][1]);
                }
            }
        }

        printf("%d\n", dp[m][k][1]);
    }


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值