链接:https://ac.nowcoder.com/acm/problem/210249
来源:牛客网
题目
在一个凹槽中放置了\ n n 层砖块,最上面的一层有\ n n 块砖,第二层有\ n-1 n−1块,……最下面一层仅有一块砖。第 \ i i 层的砖块从左至右编号为1,2,\dots i1,2,…i,第i层的第j块砖有一个价值a[i,j](a[i,j]\leq 50)a[i,j](a[i,j]≤50)。下面是一个有5层砖块的例子:
如果你要敲掉第 \ i i层的第 \ j j 块砖的话,若 \ i=1 i=1,你可以直接敲掉它,若\ i>1 i>1,则你必须先敲掉第 \ i-1 i−1 层的第j和第\ j+1 j+1 块砖。
你的任务是从一个有n(n\leq 50)n(n≤50)层的砖块堆中,敲掉 (m\leq 500)(m≤500)块砖,使得被敲掉的这些砖块的价值总和最大。
解析
要选
a
[
i
]
[
j
]
a[i][j]
a[i][j]那么至少要选
a
[
i
−
1
]
[
j
+
1
]
a[i-1][j+1]
a[i−1][j+1]
所以对于每个
d
p
[
i
]
[
j
]
[
k
]
dp[i][j][k]
dp[i][j][k]打完了第i行j列的那块砖,一共打了k块砖
d
p
[
i
]
[
j
]
[
k
]
=
m
a
x
(
d
p
[
l
]
[
j
+
1
]
[
k
−
i
]
+
p
r
e
[
i
]
[
j
]
,
d
p
[
i
]
[
j
]
[
k
]
)
;
(
i
−
1
<
=
l
<
=
n
−
j
)
dp[i][j][k] = max(dp[l][j+1][k-i] + pre[i][j],dp[i][j][k]); (i-1 <= l <= n - j)
dp[i][j][k]=max(dp[l][j+1][k−i]+pre[i][j],dp[i][j][k]);(i−1<=l<=n−j)
其中
p
r
e
[
i
]
[
j
]
pre[i][j]
pre[i][j]表示第j列第1行到第i行的砖块分数和
注意状态转移过程中
0
<
=
i
<
=
n
−
j
+
1
0 <= i <= n - j + 1
0<=i<=n−j+1
为啥i = 0 也要更新?因为存在最优解,第j列一块砖块都不打,但是j + x列,和j - y列打了的情况,所以要求dp[0][j][k]把的值,把值传递到j - y列
洛谷中这道题砖块数范围更大,dp数组开大点就行了
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 55;
ll dp[N][N][502];
ll a[N][N];
ll pre[N][N];//前缀和
int main(){
memset(dp, 0x8f, sizeof dp);//每个字节都是0x8f,所以dp[i][j][k]一定都是负数
int n,m; cin >> n >> m;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n + 1 - i; ++j)
cin >> a[i][j];
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n + 1 - i; ++j)
pre[i][j] = pre[i-1][j] + a[i][j];
dp[0][n + 1][0] = 0;
for(int j = n; j; --j)
for(int i = 0; i <= n - j + 1; ++i)// 为啥i = 0 也要更新?要考虑 第j列一个都不选的情况,需要通过dp[0][j][l]把值传递过去
for(int k = i*(i + 1)/2; k <= m; ++k){
ll t = -1e9;
for(int l = max(i - 1,0); l <= n + 1 - (j + 1); ++l)
t = max(t,dp[l][j + 1][k-i]);
dp[i][j][k] = max(t + pre[i][j],dp[i][j][k]);
}
ll ans = 0;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n - i + 1 ; ++j)
ans = max(ans,dp[i][j][m]);
cout << ans ;
return 0;
}