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 i>1 ,则你必须先敲掉第 i − 1 i-1 i−1 层的第 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 i>1,则你必须先敲掉第 i − 1 i-1 i−1 层的第 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(k−1,i−1,j)+f(k−1,i−1,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)
(i−1,j),(i−2,j)⋯(2,j),(1,j) 都是要敲掉的。
而且我们还要敲掉
(
i
−
1
,
j
+
1
)
(i-1,j+1)
(i−1,j+1) ,但是敲掉
(
i
−
1
,
j
+
1
)
(i-1,j+1)
(i−1,j+1) 的办法不止一种,就像下面这张图:
在这张图中,我们目标敲掉
(
2
,
2
)
(2,2)
(2,2) ,绿色格子就是我们要敲掉同一列的格子,然后在红色箭头指向的两个格子中任意一个被敲掉,
(
2
,
2
)
(2,2)
(2,2) 都能敲掉。
斜上箭头指着的格子就是
(
i
−
1
,
j
+
1
)
(i-1,j+1)
(i−1,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) (i−1,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) (i−1,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(i−1,j+1),f(i,j+1),f(i+1,j+1),⋯,f(n−i,j+1)}+k=1∑iak,j=i−1⩽i′⩽n−imax{f(i′,j+1)}+k=1∑iak,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),(i−1,j),(i−2,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)=i−1⩽i′⩽n−imax(f(k−i,i′,j+1)}+p=1∑iap,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;
}