郁闷的潜水员(二维背包)

题目描述

描述 Description
潜水员为了潜水要使用特殊的装备。他有一个带2种气体的气缸:一个为氧气,一个为氮气。让潜水员下潜的深度需要各种的数量的氧和氮。潜水员有一定数量的气缸。每个气缸都有重量和气体容量。潜水员为了完成他的工作需要特定数量的氧和氮。他完成工作所需气缸的总重的最低限度的是多少?
例如:潜水员有5个气缸。每行三个数字为:氧,氮的(升)量和气缸的重量:
  3 36 120
  10 25 129
  5 50 250
  1 45 130
  4 20 119
如果潜水员需要5升的氧和60升的氮则总重最小为249 (1,2或者4,5号气缸)。
  你的任务就是计算潜水员为了完成他的工作需要的气缸的重量的最低值。
输入格式 Input Format
第一行有2整数t,a(1<=t<=21,1<=a<=79)。它们表示氧,氮各自需要的量。
  第二行为整数n (1<=n<=1000)表示气缸的个数。
  此后的n行,每行包括ti,ai,wi(1<=ti<=21,1<=ai<=79,1<=wi<=800)3整数。这些各自是:第i个气缸里的氧和氮的容量及汽缸重量。
输出格式 Output Format
一行包含一个整数,为潜水员完成工作所需的气缸的重量总和的最低值。
样例输入 Sample Input
5 60
5
3 36 120
10 25 129
5 50 250
1 45 130
4 20 119
样例输出 Sample Output
249
时间限制 Time Limitation
1s

思路

对于这一道题,我一开始看到的时候,初步判定这应该是一个二维的01背包问题,之后,并没有什么思考,就直接敲了个模板上去,但是,就是死活都调不对……

之后,我就重新看了一遍题目意思:发现并非是要刚好等于氧气的量和氮气的量,而是大于等于的最小的价值。那么,我就觉得此时背包的总量并非是输入数据中的开头给出的氧气的量和氮气的量,而是多个氧气罐和氮气罐的的总量,此时就又有一个很大的问题:总量的二维数组根本就开不下……所以,我要怎么搞??

之后,我就用了一个玄学的算法:我把之前的氧气的量和氮气的量在二维数组中全部都开到二倍,这样的话,就可以跑过那些相加之后会超出氧气的量和氮气的量的数据(比如说样例),但是万万没想到的是还是被卡掉了一组……

	for(int i=1;i<=m;++i)
		for(int j=O*2;j>=o[i];--j)
			for(int k=N*2;k>=n[i];--k)
			{
				f[j][k]=min(f[j][k], f[j-o[i]][k-n[i]]+w[i]);
				if(j>=O&&k>=N)	ans=min(ans, f[j][k]);
			}
	cout<<ans<<endl;

所以,这个不正确的算法就被我舍弃了……

之后,我就去请教了Chdy大佬,之后他提供给我了一个思路:对于一组数据来说,他不仅可以按照背包的老套路更新比其容积大的状态,他还是可以更新比起小的状态的。

比如说:有一组数据(此时我们只考虑氧气):21,19, 20(前面的要拼凑的)

那么,按照原来的背包的套路的话:21=19+2, 21=20+1,但是此时的2和1都是正无穷,所以,21这个状态是没有被更新出来的。
所以,我们可以在背包更新之前,把原来的状态都更新一遍:首先把19之内的数字都和19进行比较,之后进行更新,如果此时容量为x的价值大于19的话,那么就可以直接用19进行更新x,20同理; 之后,我们在用上面的背包的套路进行更新这样子的话也是合理的。

		for(int j=O;j>=o[i];--j)
			for(int k=N;k>=n[i];--k)
				f[j][k]=min(f[j][k], f[j-o[i]][k-n[i]]+w[i]);/*更新在19上面的部分*/
		for(int j=0;j<=o[i];++j)
			for(int k=0;k<=n[i];++k)/*更新19下面的部分*/
				f[j][k]=min(f[j][k], w[i]); 

但是,我这样的写法也是有bug的:上面的代码考虑的是氧气和氮气都比总量大的,或者是都比总量小的情况,那么其中一个比总量大,但是另一个比总量小的情况呢??这个时候就是错误的。

所以,我们思考一下正解
我们可以从四种情况 (可以合并成两大种情况) 进行考虑:O是氧气的需求量,o[i]是每个氧气管的存储的氧气;N是氮气的需求量,n[i]是每个氮气罐的存储的氮气罐, w[i]是每个氧气罐的价值(重量)。

  1. 首先分析氧气量过多的时候,当 O < = o [ i ] O<=o[i] O<=o[i] 时,说明仅i号气缸就包含了所有的氧气需求量O,此时,前i-1个气缸可以不需要装氧气,所以,我们就可以直接从氧气容积为0的状态转移而来。
    因此,当 O < = o [ i ] , N > n [ i ] O<=o[i],N>n[i] O<=o[i]N>n[i]时,我们就可以从 f [ 0 ] [ N − n [ i ] ] + w [ i ] f[0][N-n[i]] +w[i] f[0][Nn[i]]+w[i]转移而来;
    那么,同理,当 O < = o [ i ] , N < = n [ i ] O<=o[i],N<=n[i] O<=o[i]N<=n[i]时,我们就从 f [ 0 ] [ 0 ] + w [ i ] f[0][0] +w[i] f[0][0]+w[i]转移而来;

  2. 之后分析氧气量相比于需求量少的时候,当 O > o [ i ] O>o[i] O>o[i]的时候,就像是之前的最开始的背包模型,该氧气罐需要其他的氧气罐作为补充,那么我们就可以从状态前面的已经存储过若干个氧气罐的状态转移而来。
    因此,当 O > o [ i ] , N > n [ i ] O>o[i],N>n[i] O>o[i]N>n[i]的时候,我们就从状态 f [ O − o [ i ] ] [ N − n [ i ] ] f[O-o[i]][N-n[i]] f[Oo[i]][Nn[i]]转移而来;
    同时,联系上面的状态,当 O > o [ i ] , N < = n [ i ] O>o[i],N<=n[i] O>o[i]N<=n[i]的时候,我们从状态 f [ O − o [ i ] ] [ 0 ] + w [ i ] f[O-o[i]][0]+w[i] f[Oo[i]][0]+w[i]转移而来。

注意理解上面的分类方式,以及解决方法。

code

#include<bits/stdc++.h>
using namespace std;

const int nn=1003;

int O, N, m;
int o[nn], n[nn], w[nn];
int f[200][200];

inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
	return x*f;
}

int main()
{
	O=read(), N=read(), m=read(); memset(f,0x3f,sizeof(f)); f[0][0]=0;
	for(int i=1;i<=m;++i)	o[i]=read(), n[i]=read(), w[i]=read();
	for(int i=1;i<=m;++i)
	{
		for(int j=O;j>=0;--j)
			for(int k=N;k>=0;--k)
			{
				int t1=(j-o[i]>0)?(j-o[i]):0;
				int t2=(k-n[i]>0)?(k-n[i]):0;
				f[j][k]=min(f[j][k], f[t1][t2]+w[i]); 
			}
	}
	cout<<f[O][N]<<endl;
	return 0;
}

最后的回顾

在这一道题目中,最大的坑点应该是所有情况的分类,可能是想到了,但是分类不全面;也有可能是根本就没有想到分类上去;还有可能是对于分的每一部分的处理的方式不恰当……(分类保命!

总之,在做到自己已经学习过的简单的知识点的时候应该要注意题目中是否有坑点,很多时候除了模板题之外的题目都是会在模板上加以改变方式进行考察。

有部分的思路借鉴与此:潜水员问题

完……

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值