DP动态规划-爬塔(双层dp)

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的理解还是有意义的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值