【题解】洛谷P1759 通天之潜水

通天之潜水

题目背景

直达通天路·小 A 历险记第三篇

题目描述

在猴王的帮助下,小 A 终于走出了这篇荒山,却发现一条波涛汹涌的河拦在了自己的面前。河面上并没有船,但好在小 A 有 n n n 个潜水工具。由于他还要背重重的背包,所以他只能背 m m m 重的工具,又因为他的力气并不是无限的,河却很宽,所以他只能背有 v v v 阻力的工具。但是这条河下有非常重要的数据,所以他希望能够停留的时间最久。于是他找到了你,让你告诉他方案。

输入格式

三个数 m , v , n m, v, n m,v,n 如题目所说。

接下来 n n n 行,每行三个数 a i , b i , c i a_i, b_i, c_i ai,bi,ci 分别表示所含的重力,阻力,能够支撑的时间。

输出格式

第一行一个数,表示最长的时间。

接下来一行,若干个数,表示所选的物品。

样例 #1

样例输入 #1

100 100 3
50 60 289
40 10 116
50 50 106

样例输出 #1

405 
1 2

提示

对于 100 % 100 \% 100% 的数据, 1 ≤ m , v ≤ 200 1 \le m, v \le 200 1m,v200 1 ≤ n ≤ 100 1 \le n \le 100 1n100

数据保证一定有方案。

若有多种方案,输出前面尽量小的方案。


看起来是个很简单的二维费用背包是吧,只要模仿01背包的做法就可以了。
这个题为什么是绿色的?还需要输出选择物品的方案,而且题目会卡,不优化空间就会MLE,因此我们需要在滚动数组/二维数组(二位费用背包本来是三维的)的情况下,输出选择物品的方案。

为了循序渐进,先考虑三维状态的背包是如何输出选择物品的方案的,每个状态[物品数][费用1][费用2]存一个vector,加上这个vector,这个数组是四维数组了,当然网上看到有用char存的,如果不用vector改用char或许不需要优化空间直接就过了。我们需要在每次发现可以更新的时候,把子问题的物品选择方案复制一份过来,然后把当前物品的编号放进后面去。

注意:每个状态只更新了一次,因为循环只会在这个状态停留一次,那么在更新/沿用i-1的时候,只会这样被处理一次物品选择的方案,不会反复修改。换句话说,每个状态的物品选择方案,一旦更新就肯定是**使得价值最大(但是对于同样的最大价值,方案可能不唯一,首次选择用哪种方案,并不确定,需要修改代码细节来控制,详见下文)**的方案。

既然每个状态只更新一次,那么选择的“路径”是无后效性的,可以边递推边存储路径。

但是这个题真的值绿色吗? 毫无疑问我这样做hack都没过, 注意到,题目的最后,写明了要输出的方案要求前面的物品尽可能编号小,用hack1的数据试一下你就懂了

先上我的没AC的代码:

#include<iostream>
#include<vector>
using namespace std;
int m,v,n,a[101],b[101],c[101];
int f[201][201];
vector<int> items[201][201];
int main()
{
	cin>>m>>v>>n;
	for(int i=1;i<=n;i++)
	cin>>a[i]>>b[i]>>c[i];
	for(int i=1;i<=n;i++)
	{
		for(int j=m;j>=a[i];j--)
		{
			for(int k=v;k>=b[i];k--)
			{
				if(f[j-a[i]][k-b[i]]+c[i]>f[j][k])
				{
					f[j][k]=f[j-a[i]][k-b[i]]+c[i];
					items[j][k]=items[j-a[i]][k-b[i]];
					items[j][k].push_back(i);
				}
				else items[j][k]=items[j][k];
			}
		}
	}
	cout<<f[m][v]<<endl;
	for(int i=0;i<items[m][v].size();i++)
	{
		cout<<items[m][v][i]<<" ";
	}
	return 0;
}

分析可知我的代码的更新条件是“惰性”的,必须要严格增才会更新,也就是说,我的代码会使得最后一个物品的编号尽可能小
如果加了等于号,那么会使得最后一个物品的编号尽可能大

敏锐的你一定会察觉到,只要把物品的顺序反过来,那么就变成了第一个物品的编号尽可能小,最后再反序输出就得到正确顺序的答案了

AC代码如下:

#include<iostream>
#include<vector>
using namespace std;
int m,v,n,a[101],b[101],c[101];
int f[201][201];
vector<int> items[201][201];
int main()
{
	cin>>m>>v>>n;
	for(int i=1;i<=n;i++)
	cin>>a[i]>>b[i]>>c[i];
	for(int i=n;i>=1;i--)
	{
		for(int j=m;j>=a[i];j--)
		{
			for(int k=v;k>=b[i];k--)
			{
				if(f[j-a[i]][k-b[i]]+c[i]>=f[j][k])
				{
					f[j][k]=f[j-a[i]][k-b[i]]+c[i];
					items[j][k]=items[j-a[i]][k-b[i]];
					items[j][k].push_back(i);
				}
				else items[j][k]=items[j][k];
			}
		}
	}
	cout<<f[m][v]<<endl;
	for(int i=items[m][v].size()-1;i>=0;i--)
	{
		cout<<items[m][v][i]<<" ";
	}
	return 0;
}
  • 29
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值