DP动态规划-爬塔(双层dp)
比赛来源:牛客 - 中国计量大学现代科技学院第四届“中竞杯”程序设计校赛
题目--------F题
description:
高川最喜欢的游戏当属 Slay the Spire,这是一款爬塔游戏,你需要从一座塔的底部一直爬到顶部,在爬塔的过程中,塔的每一层都有许多的宝物等你来拿。
高川从塔的左侧开始攀爬,从底部爬到顶部,再从右侧从顶部逐步下到底部。塔总共有 n 层,每一层都有很多宝物从左到右排列。在左侧攀爬时,他只能从每层的最左边按顺序取宝物,在右侧下降时,他只能从每层的最右边按顺序取宝物。每个宝物都有一个价值,他最多拿 m 个宝物,他想知道自己从塔上下来时,最多可以拿的宝物价值和是多少
Sample:
解题思路:
- 从题目中可以看出每一层都是独立的,每一层中都有一个拿取宝物的顺序限制,这也是题目的核心。
- 所以首先,我们设置dpPerLine[i][j]来表示第i层选j个宝藏时,在这层的最大宝藏价值。可以用前后缀和来快速实现。
- 然后,我们那拿到pPerLine后,可以直接去动态规划总共选j个宝物时的最大价值dp[j],最后得到最后的解dp[m]。具体看代码注释,这里注意j和k要逆序遍历,因为要保证每一层拿少数的状态值不会去影响拿多数时的状态值,不然则可能会出现连续拿一层同一个高价值宝藏的情况。具体情况可以自己去试一试。
源码与注释:
#include<iostream>
#include<algorithm>
using namespace std;
int c[110][110];//宝藏权重
int pre[110][110], suf[110][110];//每一层的前缀和、后缀和
int x[110];//每一层的宝藏数量
int dpPerLine[110][110];//第i层选j个宝藏时,在这层的最大宝藏价值。
int dp[10010];//拿i个宝藏的最大价值
int main()
{
int n, m;//塔层数,宝藏可选数量
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> x[i];
for (int j = 1; j <= x[i]; j++) {
cin >> c[i][j];
pre[i][j] = pre[i][j - 1] + c[i][j];//计算前缀和
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <=x[i]; j++)
suf[i][j] = pre[i][x[i]] - pre[i][x[i]-j];//计算后缀和
}
//dp第i层选j个宝藏时,在这层的最大宝藏价值。
for (int i = 1; i <= n; i++) {//第i层
for (int j = 1; j <= x[i]; j++) {//在i层选j个宝藏
for (int k = 0; k <= j; k++) {//此时方案为前缀选k个
dpPerLine[i][j] = max(dpPerLine[i][j], pre[i][k] + suf[i][j - k]);
}
}
}
//num动态表示此时最多可以拿到宝藏的数量
int num = 0;
for (int i = 1; i <= n; i++) {//遍历至i层(根据数据读取时的顺序)
num += x[i];
//此时一共选j个宝藏,
//j一定小于 总共可选数量上限m 和 现在可选数量上限num
for (int j = min(num, m); j >=1 ;j--) {
//我们选择在这一层选k个宝藏,遍历k
//k一定小于 本层最大宝藏数量x[i] 和 此时一共选的宝藏数j
for (int k = min(x[i],j); k>=1 ; k--) {
//此时共选j个数量,分别是来自这一层的k个和前面的j-k个
dp[j] = max(dp[j] , dp[j - k] + dpPerLine[i][k]);
}
}
}
cout << dp[m] << endl;
return 0;
}
纯源码:
#include<iostream>
#include<algorithm>
using namespace std;
int pre[110][110],suf[110][110],c[110][110];
int x[110];
int dpPerLine[110][110],dp[10010];
int main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> x[i];
for (int j = 1; j <= x[i]; j++) {
cin >> c[i][j];
pre[i][j] = pre[i][j - 1] + c[i][j];
}
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= x[i]; j++)
suf[i][j] = pre[i][x[i]] - pre[i][x[i] - j];
for (int i = 1; i <= n; i++)
for (int j = 1; j <= x[i]; j++)
for (int k = 0; k <= j; k++)
dpPerLine[i][j] = max(dpPerLine[i][j], pre[i][k] + suf[i][j - k]);
int num = 0;
for (int i = 1; i <= n; i++) {
num += x[i];
for (int j = min(num, m); j >= 1; j--)
for (int k = min(x[i], j); k >= 1; k--)
dp[j] = max(dp[j], dp[j - k] + dpPerLine[i][k]);
}
cout << dp[m] << endl;
return 0;
}
结果:
可以看到这个方法速度还是比较快的,但和16+ms的大佬比还是比不了der,如果觉得这个方法太繁琐可以去比赛里面看看大佬们的代码,不过这个方法对于加深dp的理解还是有意义的。