ACM: 动态规划题 《黑书…

艺术馆的火灾

背景描述

这幢古老的建筑是一个艺术馆,它珍藏着上百件绘画、雕塑以及其他艺术品,就连建筑本身也是一件艺术。但是,岁月并不在乎它的精致与美丽,时光在渐渐剥夺着这幢木屋的生命。终于,在一个月色昏暗的夜晚,它着火了。

艺术馆是一幢两层的小楼,每一层有N个房间,每个房间中收藏的艺术品的价值都可以用一个正整数来表示。下面是一个N=4的例子。

                     ACM: <wbr>动态规划题 <wbr>《黑书》- <wbr>艺术馆的火灾

在这个例子中,二层楼的第四个房间中艺术品的价值最大,为60。而一层楼的第四个房间中艺术品的价值仅为20。

在消防队员火速赶到的时候,火势已经蔓延了整个建筑,消防队员们观察每个房间中的火势,将它们分别用一个正整数来表示。在上面的例子中,各房间中的火势可能如下。

                      ACM: <wbr>动态规划题 <wbr>《黑书》- <wbr>艺术馆的火灾

你可以看到,二层楼的第四个房间中火势最强,为70。而一层楼的第三个房间中火势较弱,为20。

由于火情紧急,消防队员们准备使用一种新型的灭火器。这种灭火器只能发射K次,每次发射将覆盖一个矩形的区域(矩形的高度可以是1也可以是2)。它的威力巨大,所到之处不但火焰会被扑灭,就连同在一处的艺术品也难以幸免。因此,如何善用这种灭火器成了最大的问题。

一个例子:如果灭火器的一次发射覆盖了下图阴影所示的2×2矩形区域,那么这四个房间的火势和艺术品价值都将成为0。

                      ACM: <wbr>动态规划题 <wbr>《黑书》- <wbr>艺术馆的火灾

任务说明

给出艺术馆每层的房间数N和灭火器的发射次数K,以及每个房间中艺术品的价值和火势,你需要决定灭火器每次应该怎样发射(也可以不发射),才能将这次火灾的损失降到最低限度。这个损失用你所摧毁的艺术品的总价值,加上剩余的火势总值(这些火焰将需要消防队员们亲身去扑灭)来衡量。

 

输入数据

输入文件的第一行有两个整数N(1 <= N <= 100)、K(1 <= K <= 10),分别表示艺术馆中每层的房间数和灭火器的发射次数。

接下来的两行每行有N个整数,其中第4-i行的第j个整数Vi,j表示的是第i层第j个房间中艺术品的价值(1 <= i <= 2,1 <= j <= N,1 <= Vi,j <= 10000)。

再接下来的两行每行也有N个整数,其中第6-i行的第j个整数Fi,j表示的是第i层第j个房间中的火势(1 <= i <= 2,1 <= j <= N,1 <= Fi,j <= 10000)。

输出数据

计算最小的损失。

 

样例输入:

4 1
40 50 30 60
30 30 40 20
50 40 50 70
40 50 20 30

4 2
40 50 30 60
30 30 40 20
50 40 50 70
40 50 20 30

4 3
40 50 30 60
30 30 40 20
50 40 50 70
40 50 20 30

4 4
40 50 30 60
30 30 40 20
50 40 50 70
40 50 20 30

4 5
40 50 30 60
30 30 40 20
50 40 50 70
40 50 20 30

4 1
40 50 30 60
30 30 40 200
50 40 50 70
40 50 20 30

3 1
10 30 10
10 10 30
30 10 30
30 30 10

 

样例输出:

300

290

280

270

270

320

100

 

题意: 在一个2*N的矩阵中选取K个子矩阵, 使得损失最小. 子矩阵的大小可以使1*L or 2*L, 其中1和2

      表示矩阵高度, L表示矩阵长度(1<=L<=N);

 

解题思路: (想了2天, 终于想明白!!!)

     1. 题目给出2个权值, Val价值和F火势, 最后结果: min(摧毁价值+剩余火势), 灭火器最多发射

        K次, 每次灭火的区域大小1*L or 2*L.

     2. 开始想到状态是dp[i][j][k]表示第1行前i列和第2行前j列选择了k个矩阵还能获取最小损失.

        但是在状态转移时, 无法确定上一个状态的选择情况, 状态方程写不出. 改了一种方法:

        设: dp[k][i][j]表示前i列中选取了k个矩形, 第i列的选取情况是j, 此时还能获取的最小

        损失.

            (1). 状态方程: dp[k][i][j] = min( dp[i+1][p][nextk]+getLoss(i+1, p) )

                 其中p表示i+1列的选取情况, nextk表示k+1 or k不变;

            (2). 每列有4种选取情况: 0: 上下层都不用灭火器

                                    1: 只是上层使用灭火器

                                    2: 只是下层使用灭火器

                                    3: 上下层都使用灭火器

                  这里我们增设一种情况: 4: 当前列上下层同前一列上下层都使用灭火器.

                  因为会出现一种情况: 当j = 3时, p = 1 or 2, 如下图(举例p = 1)

                 ACM: <wbr>动态规划题 <wbr>《黑书》- <wbr>艺术馆的火灾

                  中间列为j(当前), 最后一列为p(下一列), 显然第一种情况下, 因为第一列

                  选取情况是2造成, 当p = 1时, 虽然当前列与下一列选取情况不同应该nextk

                  = k+1,但是前一列的结果造成nextk = k. 第二种情况是最想看见的, 为了

                  区别第一种情况, 所以增设了一种选取情况.

             (3). nextk = (k+1) or  nextk = k, 显然满足 p == 0 || j == p || j == 4

                  当前列上下层都不使用灭火器, 当前列状态与前一列选取情况相同, 上一列

                  已经标记为特殊状态要求当前列上下层同前一列上下层都使用灭火器. 此时

                  nextk = k.

      3. 从设的状态可以知道是用向前递推法, 结果DP(0, 0, 0); 记忆化搜索即可.

 

代码:

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
#define MAX 105
const int INF = (1<<29);

int n, K;
int val[3][MAX], f[3][MAX];
int dp[12][MAX][5];
int result;

inline int min(int a, int b)
{
 return a < b ? a : b;
}

int getLoss(int i, int j) //0:上下不用, 1:上用, 2:下用, 3:灭火器覆盖最少1个小矩形, 4:覆盖至少2个矩形
{
 if(j == 0) return f[1][i]+f[2][i];
 else if(j == 1) return val[1][i]+f[2][i];
 else if(j == 2) return val[2][i]+f[1][i];
 else return val[1][i]+val[2][i];
}

int DP(int k, int i, int j)
{
 if(dp[k][i][j] != -1) return dp[k][i][j];
 if(i == n) return 0;

 int ans = INF, temp;
 int nextk;
 for(int p = 0; p <= 3; ++p)
 {
  if(p == 3 && (j == 1 || j == 2 || j == 4)) p = 4;
  if( p == 0 || j == p || j == 4 ) nextk = k;
  else nextk = k+1;
  
  if(nextk <= K) temp = DP(nextk, i+1, p)+getLoss(i+1, p);
  else continue;
  ans = min(ans, temp);
 }
 return dp[k][i][j] = ans;
}

int main()
{
 int i, j;
// freopen("input.txt", "r", stdin);
 while( scanf("%d %d", &n, &K) != EOF )
 {
  for(i = 1; i <= 2; ++i)
  {
   for(j = 1; j <= n; ++j)
    scanf("%d", &val[i][j]);
  }

  for(i = 1; i <= 2; ++i)
  {
   for(j = 1; j <= n; ++j)
    scanf("%d", &f[i][j]);
  }

  memset(dp, -1, sizeof(dp));
  result = DP(0, 0, 0);
  printf("%d\n", result);
 }
 return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值