背包问题

1.0-1背包

http://acm.hdu.edu.cn/showproblem.php?pid=2602  

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

int dp[1002];
int vol[1002];
int val[1002];

int main(){

	//freopen("test.txt", "r", stdin);

	int T;

	cin >> T;

	while (T--){

		int N, V;
		cin >> N >> V;

		for (int i = 1; i <= N; i++){
			cin >> val[i];
		}
		for (int i = 1; i <= N; i++){
			cin >> vol[i];
		}

		for (int j = 0; j <= V; j++){
			dp[j] = 0;
		}

		for (int i = 1; i <= N; i++){
			for (int j = V; j >= 0; j--){
				if (j >= vol[i]){
					dp[j] = max(dp[j], dp[j - vol[i]] + val[i]);
				}
			}
		}

		cout << dp[V]<<endl;

	}



	return 0;
}


Description:
话说月光家里有许多玩具,最近他又看上了DK新买的“擎天柱”,就想用自己的跟DK的换。每种玩具都有特定的价格,价格为整数。只有月光拿出的玩具的总价格与“擎天柱”的价格相等才能换得“擎天柱”。同时,月光还希望能用最少的玩具数换回“擎天柱”。请问,月光能顺利得到梦寐以求的“擎天柱”吗?

Input:
输入数据包含多组;对于每组数据,第一行为一个正整数n(1 ≤n≤10); 表示月光手头有n个玩具。接下来一行有n个正整数P1,P2,……,Pn(1 ≤ Pi ≤ 1000),Pi为第i个玩具的所对应的价格。最后一行为一个正整数m(1 ≤ m ≤10000),为“擎天柱”的价格。

Output:
对于每组数据,如果能换得“擎天柱”则输出最少玩具数;否则,输出“-1”。

Sample Input:
3
1 2 3
4
4
4 3 3 5
2
Sample Output:
2
-1


#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

int dp[10002];
int vol[1002];
//int val[1002];

int main(){

	freopen("test.txt", "r", stdin);

	int n;

	while (cin>>n){

		for (int i = 1; i <= n; i++){
			cin >> vol[i];
		}

		int m;
		cin >> m;

		dp[0] = 0;
		for (int j = 1; j <= m; j++){
			dp[j] = 1000;//比n大即可
		}

		for (int i = 1; i <= n; i++){
			for (int j = m; j >= 0; j--){
				if (j >= vol[i]){//>=
					dp[j] = min(dp[j], dp[j - vol[i]] + 1);//min
				}
			}
		}

		if (dp[m] == 1000){
			cout << -1 << endl;
		}
		else{
			cout << dp[m] << endl;
		}

	}



	return 0;
}


2.背包问题的初始化

想用刚才的方法做?发现题目里多了什么条件没?

       对了!那就是“只有月光拿出的玩具的总价格与“擎天柱”的价格相等才能换得“擎天柱”这句。 换句话说题目要求的不仅仅是最优值而且要求你求的是“能把包装满”的最优值!!!

       怎么办?难道就这样束手无策了么?

解答:

0-1背包也是背包问题的最常见的两种问法:

一是要求“恰好装满背包”时的最优解

二是“没有要求必须把背包装满”。

这两种问法的实现方法不同点主要在初始化上。

如果是第一种问法,要求恰好装满背包,那么在初始化时除了f[0]为0其它f[1..V]均设为-∞(有时是+),这样就可以保证最终得到的f[N]是一种恰好装满背包的最优解。

如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将f[0..V]全部设为0。
为什么呢?

可以这样理解:初始化的f数组事实上就是在没有任何物品可以放入背包时的合法状态。如果要求背包恰好装满,那么此时只有容量为0的背包可能被价值为0的nothing“恰好装满”,其它容量的背包均没有合法的解,属于未定义的状态,它们的值就都应该是-∞了。如果背包并非必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的价值为0,所以初始时状态的值也就全部为0了。

这个小技巧完全可以推广到其它类型的背包问题,后面也就不再对进行状态转移之前的初始化进行讲解。

3.完全背包

http://acm.hdu.edu.cn/showproblem.php?pid=1248  寒冰王座

转化为01背包问题求解
解题思路:

       既然01背包问题是最基本的背包问题,那么我们可以考虑把完全背包问题转化为01背包问题来解。最简单的想法是,考虑到第i种物品最多选V/c[i]件,于是可以把第i种物品转化为V/c[i]件费用及价值均不变的物品,然后求解这个01背包问题。这样完全没有改进基本思路的时间复杂度,但这毕竟给了我们将完全背包问题转化为01背包问题的思路:将一种物品拆成多件物品。

       但我们有更优的O(VN)的算法。这个算法使用一维数组,先看伪代码:

    for i=1..N

        for v=0..V

              f[v]=max{f[v],f[v-c[i]]+w[i]};

       你会发现,这个伪代码与0-1背包的伪代码只有v的循环次序不同而已。为什么这样一改就可行呢?首先想想为什么0-1背包中要按照v=V..0的逆序来循环。这是因为要保证第i次循环中的状态f[i][v]是由状态f[i-1][v-c[i]]递推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的子结果f[i-1][v-c[i]]。而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第i种物品”这种策略时,却正需要一个可能已选入第i种物品的子结果f[i][v-c[i]],所以就可以并且必须采用v=0..V的顺序循环。这就是这个简单的程序为何成立的道理。

       这个算法也可以以另外的思路得出。例如,基本思路中的状态转移方程可以等价地变形成这种形式:f[i][v]=max{f[i-1][v],f[i][v-c[i]]+w[i]},将这个方程用一维数组实现,便得到了上面的伪代码。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

int dp[10002];
//int vol[1002];
//int val[1002];

int main(){

	//freopen("test.txt", "r", stdin);

	int vol[] = {0, 150, 200, 350 };

	int T;
	cin >> T;

	while (T--){
		int N;
		cin >> N;

		for (int j = 0; j <= N; j++){
			dp[j] = 0;
		}

		for (int i = 1; i <= 3; i++){
			for (int j = 0; j <= N;j++){
				if (j >= vol[i]){//>=
					dp[j] = max(dp[j], dp[j - vol[i]] + vol[i]);
				}
			}
		}

		cout << N - dp[N] << endl;

	}



	return 0;
}
注意这题相当将重量也同时当做价值


4.多重背包

转化为01背包问题

另一种好想好写的基本方法是转化为01背包求解:把第i种物品换成n[i]件01背包中的物品,则得到了物品数为Σn[i]的01背包问题,直接求解,复杂度仍然是O(V*Σn[i])。

http://acm.hdu.edu.cn/showproblem.php?pid=2191


#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

int dp[102];
int vol[102];
int val[102];
int num[102];

int main(){

	//freopen("test.txt", "r", stdin);

	int T;
	cin >> T;

	while (T--){
		int n, m;
		cin >> n >> m;
		for (int i = 1; i <= m; i++){
			cin >> vol[i] >> val[i] >> num[i];
		}

		for (int i = 0; i <= n; i++){
			dp[i] = 0;
		}

		for (int i = 1; i <= m; i++){
			for (int k = 1; k <= num[i]; k++){
				for (int j = n; j >= 0; j--){
					if (j >= vol[i]){
						dp[j] = max(dp[j], dp[j - vol[i]] + val[i]);
					}
				}
			}
		}

		cout << dp[n] << endl;

	}


	return 0;
}

还可以

 方法是:将第i种物品分成若干件物品,其中每件物品有一个系数,这件物品的费用和价值均是原来的费用和价值乘以这个系数。使这些系数分别为1,2,4,...,2^(k-1),n[i]-2^k+1,且k是满足n[i]-2^k+1>0的最大整数。例如,如果n[i]为13,就将这种物品分成系数分别为1、2、4、6的四件物品。



大部分转自http://blog.csdn.net/hackbuteer1/article/details/7178690

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值