敲砖块 动态规划

https://blog.csdn.net/bell041030/article/details/89790142

csdn博客观看效果更佳

注意:感谢 Enquir大佬1093725598yr大佬 提供的hack数据,本题解仅提供正确思路,代码仅供参考,感谢 Jozky大佬 提出代码中的问题,现在代码已修复,并修了 LaTeX \LaTeX LATEX

Hack 数据

2 1
2 1
2
Answer 2
5 7
14 15 4 3 23
33 33 76 2
2 13 11
22 23
31
Answer 178

敲砖块

题目描述

在一个凹槽中放置了 N N N 层砖块,最上面的一层有 N N N 块砖,从上到下每层依次减少一块砖。每块砖都有一个分值,敲掉这块砖就能得到相应的分值,如图所示。

在这里插入图片描述

如果你想敲掉第 i i i 层的第 j j j 块砖的话,若 i = 1 i=1 i=1 ,你可以直接敲掉它,若 i > 1 i>1 i1 ,则你必须先敲掉第 i − 1 i-1 i1 层的第 j j j 和第 j + 1 j+1 j+1 块砖。

你现在可以敲掉最多 M M M 块砖,求得分最多能有多少。

输入格式

输入文件第一行有两个正整数 N N N M M M

接下来的 N N N 行,描述这 N N N 层砖块上的分值 A [ i , j ] A[i,j] A[i,j],满足0 ≤ A [ i , j ] ≤ 100 ≤A[i,j]≤100 A[i,j]100

输出格式

仅一行,包含一个整数,为最大的得分。

输入样例
4 5
2 2 3 4
8 2 7
2 3
49
输出样例
19

解题思路

题目大意:给你一个像上面这样的三角形,如果你想敲掉第 i i i 层的第 j j j 块砖的话,若 i = 1 i=1 i=1,你可以直接敲掉它,若 i > 1 i>1 i1,则你必须先敲掉第 i − 1 i-1 i1 层的第 j j j 和第 j + 1 j+1 j+1 块砖,求得分最多能有多少。

对于这种最值问题,脑海中第一个想法就是:动态规划

看着题目中的条件,一个隐隐约约的转移方程在脑海中浮现:
f ( k , i , j ) f(k,i,j) f(k,i,j) 表示敲第 k k k 块砖敲到 ( i , j ) (i,j) (i,j) 的最大值,

f ( k , i , j ) = max ⁡ { f ( k , i , j ) , f ( k − 1 , i − 1 , j ) + f ( k − 1 , i − 1 , j + 1 ) } f(k,i,j)=\max\{f(k,i,j),f(k-1,i-1,j)+f(k-1,i-1,j+1)\} f(k,i,j)=max{f(k,i,j),f(k1,i1,j)+f(k1,i1,j+1)}

老哥,能不能别这么冲动 很显然,我们可以得到一个错解,并且我们很快意识到,如果是以这种解法去做的话,是有后效性的,而动态规划是无后效性的,简单来说,我们不能让过去的决策影响未来的决策,不走回头路。

我们的思考陷入了僵局,不如换个角度思考问题,既然从上往下,从下往上推都不行,不妨试试从左往右推或者从右往左推
我们先来整理一下这个三角形:

三角形

紧接着,我们要在这个三角形上发掘一些有用的信息,如果我们要敲掉 ( i , j ) (i,j) (i,j) ,那么 ( i − 1 , j ) , ( i − 2 , j ) ⋯ ( 2 , j ) , ( 1 , j ) (i-1,j),(i-2,j)\cdots(2,j),(1,j) (i1,j),(i2,j)(2,j),(1,j) 都是要敲掉的。
而且我们还要敲掉 ( i − 1 , j + 1 ) (i-1,j+1) (i1,j+1) ,但是敲掉 ( i − 1 , j + 1 ) (i-1,j+1) (i1,j+1) 的办法不止一种,就像下面这张图:

在这里插入图片描述

在这张图中,我们目标敲掉 ( 2 , 2 ) (2,2) (2,2) ,绿色格子就是我们要敲掉同一列的格子,然后在红色箭头指向的两个格子中任意一个被敲掉, ( 2 , 2 ) (2,2) (2,2) 都能敲掉。
斜上箭头指着的格子就是 ( i − 1 , j + 1 ) (i-1,j+1) (i1,j+1) ( 1 , 3 ) (1,3) (1,3)

下面那个格子 ( 2 , 3 ) (2,3) (2,3) 如果被敲掉了,那么根据我们上面的规则, ( 1 , 3 ) (1,3) (1,3) 一定会被敲掉,因为这两个格子在同一列

由此我们可以知道,对于目标 ( i , j ) (i,j) (i,j)要敲掉 ( i − 1 , j + 1 ) (i-1,j+1) (i1,j+1) 可以去敲 ( i − 1 , j + 1 ) , ( i , j + 1 ) , ( i + 1 , j + 1 ) , ⋯   , ( n − ( i + 1 ) + 1 , j + 1 ) (i-1,j+1),(i,j+1),(i+1,j+1),\cdots,(n-(i+1)+1,j+1) (i1,j+1),(i,j+1),(i+1,j+1),,(n(i+1)+1,j+1)

好了,到这一步我们得出一个比较明显的结论:
f ( i , j ) = max ⁡ { f ( i − 1 , j + 1 ) , f ( i , j + 1 ) , f ( i + 1 , j + 1 ) , ⋯   , f ( n − i , j + 1 ) } + ∑ k = 1 i a k , j = max ⁡ i − 1 ⩽ i ′ ⩽ n − i { f ( i ′ , j + 1 ) } + ∑ k = 1 i a k , j \begin{aligned} f(i,j)&=\max\{f(i-1,j+1),f(i,j+1),f(i+1,j+1),\cdots,f(n-i,j+1)\} + \sum_{k=1}^{i}a_{k,j}\\ &=\max_{i-1 \leqslant i'\leqslant n-i}\{f(i',j+1)\}+\sum_{k=1}^ia_{k,j} \end{aligned} f(i,j)=max{f(i1,j+1),f(i,j+1),f(i+1,j+1),,f(ni,j+1)}+k=1iak,j=i1inimax{f(i,j+1)}+k=1iak,j

我们再将敲了第几个考虑进去, f ( k , i , j ) f(k,i,j) f(k,i,j) 表示已经敲了 k k k 个且第 k k k 个敲了 ( i , j ) (i,j) (i,j) 的最大的分情况,根据上面的规则,当前一步敲了 ( i , j ) , ( i − 1 , j ) , ( i − 2 , j ) ⋯ ( 2 , j ) , ( 1 , j ) (i,j),(i-1,j),(i-2,j)\cdots(2,j),(1,j) (i,j),(i1,j),(i2,j)(2,j),(1,j) 一共 i i i 块砖,可得式子:

f ( k , i , j ) = max ⁡ i − 1 ⩽ i ′ ⩽ n − i ( f ( k − i , i ′ , j + 1 ) } + ∑ p = 1 i a p , j f(k,i,j)=\max_{i-1\leqslant i' \leqslant n-i}(f(k-i,i',j+1)\} + \sum_{p=1}^{i}a_{p,j} f(k,i,j)=i1inimax(f(ki,i,j+1)}+p=1iap,j

我们的答案就是在所有的 f ( k , i , j ) f(k,i,j) f(k,i,j) 里寻找的最大值了。


代码

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>

using namespace std;
int n,m,a[105][105],ans = 0 ;
int f[1280][55][55],sum[55][55];//sum(i,j) 表示第 i 列前 j 行的得分前缀和 
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]);
			sum[j][i] = sum[j][i - 1] + a[i][j];
		}
	memset(f,-1,sizeof(f));
	for(int i = 0;i <= n + 1;i ++) f[0][0][i] = 0;//初始化
	for(int j = n;j >= 1;j --)//枚举列,从最后一列开始
	{
		for(int i = 0;i <= n - j + 1;i ++)//枚举行,可以不选这一行,从 0 开始
		{
			for(int k = i;k <= m;k ++)//枚举敲了多少砖块
				for(int p = max(i - 1,0);p <= n - j;p ++)//枚举从上一列的哪一列转移过来,要避免数组越界
					if(f[k - i][p][j + 1] != -1)//有合法的情况
					{
						f[k][i][j] = max(f[k][i][j],f[k - i][p][j + 1] + sum[j][i]);//转移
						ans = max(ans,f[k][i][j]);//记录答案
					}
		}
	}
	printf("%d",ans);
	return 0;
} 
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值