背包问题小结

这个周好好总结了背包问题,下面简单总结一下背包问题,也方便以后忘了可以来复习

背包问题最基础的就是01背包,其他的背包问题都可以通过添加各种附加条件这样的方式转化为01背包。01背包就按字面意思来理解,每个物品只有一件,0是false1是true就是这件物品装还是不装,做出选择,根据这个选择我们也可以写出状态转移方程,定义一个二维数组f[i][v]表示前i件物品部分或全部放入容量为v的背包可以获得的最大价值,根据题意如果第i件物品不放入背包,那么当前背包的最大价值和前一件物品的状态是相同的,那么状态转移方程就是f[i][v]=f[i-1][v],如果第i件物品不放入背包,状态转移方程就是f[i][v]=f[i-1][v-w[i]]+c[i],w[i]是第i件物品的重量,c[i]是第i件物品的价值,解释一下就是如果第i件物品放入背包,那么当前背包的价值就等于在前一件物品,并且背包里没有第i件物品的最大价值再加上第i件物品的价值。最后取出二者的最大值即可,即

f[i][v]=max(f[i-1][v-w[i]]+c[i],f[i-1][v]);

用二维数组f[maxn][maxn]存储子问题的时候,如果maxn超过2000就会超过内存限制,代码没办法运行,这个时候我们可以用一位数组的模式来优化空间复杂度,即

f[v]=max(f[v],f[v-w[i]]+c[i]);

意思的话跟二维数组没有太大区别,一看就能明白

看一道01背包的遍历

01背包问题


时间限制: 1000 ms         内存限制: 65536 KB
提交数: 12659     通过数: 7806

【题目描述】

一个旅行者有一个最多能装 MM 公斤的背包,现在有 nn 件物品,它们的重量分别是W1,W2,...,WnW1,W2,...,Wn ,它们的价值分别为C1,C2,...,CnC1,C2,...,Cn ,求旅行者能获得最大总价值。

【输入】

第一行:两个整数,MM (背包容量,M≤200M≤200 )和NN (物品数量,N≤30N≤30 );

第2..N+12..N+1 行:每行二个整数Wi,CiWi,Ci ,表示每个物品的重量和价值。

【输出】

仅一行,一个数,表示最大总价值。

【输入样例】

10 4
2 1
3 3
4 5
7 9

【输出样例】

12
#include<cstdio>
using namespace std;
const int N=201;
int qread () {
	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<<3)+(x<<1)+ch-48;
		ch=getchar();
	}
	return x*f;
}
int i,j,m,n,wi,ci,f[N]={0};
int main () {
	m=qread();n=qread();
	for(i=1;i<=n;i++) {
		wi=qread();ci=qread();
		for(j=m;j>=wi;j--)
			if(f[j]<f[j-wi]+ci) f[j]=f[j-wi]+ci; 
	}
	printf("%d",f[m]);
	return 0;
}

 

下面说一下完全背包

完全背包和01背包只有一点区别就是,01背包每件物品有无数件,而完全背包每件物品有无数件,并不是每件物品取或者不取,而变成了每件物品可以取多件遍历顺序也有所区别,按照0到v的逆顺序提交 ,看一道具体的介绍完全背包概念的例题来看看状态转移方程,其实区别并不是很大,可以简单的通过一维数组解决

完全背包问题


时间限制: 1000 ms         内存限制: 65536 KB
提交数: 10578     通过数: 5696

【题目描述】

设有nn 种物品,每种物品有一个重量及一个价值。但每种物品的数量是无限的,同时有一个背包,最大载重量为MM ,今从nn 种物品中选取若干件(同一种物品可以多次选取),使其重量的和小于等于MM ,而价值的和为最大。

【输入】

第一行:两个整数,MM (背包容量,M≤200M≤200 )和NN (物品数量,N≤30N≤30 );

第2..N+12..N+1 行:每行二个整数Wi,CiWi,Ci ,表示每个物品的重量和价值。

【输出】

仅一行,一个数,表示最大总价值。

【输入样例】

10 4
2 1
3 3
4 5
7 9

【输出样例】

max=12
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
const int maxn = 205;
using namespace std;
int w[maxn],c[maxn],dp[maxn];
int main(){
	int V,N;
	cin>>V>>N;
	for(int i=1;i<=N;i++) cin>>w[i]>>c[i];
	for(int i=1;i<=N;i++){
		for(int v=w[i];v<=V;v++){
			dp[v] = max(dp[v] , dp[v-w[i]] + c[i]);
		}
	}
	cout<<"max="<<dp[V];
	return 0;
} 

接下来就是多重背包问题,介绍概念的例题就是庆功宴,这个问题的区别就是有n种物品,每种物品并不是每件都可用,会有一个n[i]数组存储每种物品可用的数量,这个问题的基本方程需要将完全背包的方程做一下更改,对于第i件物品有n[i]+1种策略,取0件1件直到n[i]件,令f[i][v]表示前i种物品放入背包的最大价值 

状态转移方程就是

f[i][v]=max(f[i-1][v-k*w[i]]+k*c[i],f[i-1][v]);

 下面看一下庆功宴这道题目

庆功会


时间限制: 1000 ms         内存限制: 65536 KB
提交数: 8353     通过数: 4811

【题目描述】

为了庆贺班级在校运动会上取得全校第一名成绩,班主任决定开一场庆功会,为此拨款购买奖品犒劳运动员。期望拨款金额能购买最大价值的奖品,可以补充他们的精力和体力。

【输入】

第一行二个数n(n≤500),m(m≤6000),其中n代表希望购买的奖品的种数,m表示拨款金额。

接下来n行,每行3个数,v、w、s,分别表示第I种奖品的价格、价值(价格与价值是不同的概念)和能购买的最大数量(买0件到s件均可),其中v≤100,w≤1000,s≤10。

【输出】

一行:一个数,表示此次购买能获得的最大的价值(注意!不是价格)。

【输入样例】

5 1000
80 20 4
40 50 9
30 50 7
40 30 6
20 20 1

【输出样例】

1040
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
const int maxn = 6005;
using namespace std;
int w[maxn],c[maxn],p[maxn],dp[maxn];
int main(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++)  cin>>w[i]>>c[i]>>p[i];
	
	for(int i=1;i<=n;i++){
		for(int j=m;j>=0;j--){
			for(int k=0;k<=p[i];k++){
				if(j - k * w[i] < 0) break;
			dp[j] = max(dp[j],dp[j-k*w[i]]+k*c[i]); 
			}
		}
	}
	cout<<dp[m];
	return 0;
} 

 

 剩下的分组背包和二维费用背包都不是很常见就先没有总结

再看两道我这个周做的vj的背包问题

电子科大本部食堂的饭卡有一种很诡异的设计,即在购买之前判断余额。如果购买一个商品之前,卡上的剩余金额大于或等于5元,就一定可以购买成功(即使购买后卡上余额为负),否则无法购买(即使金额足够)。所以大家都希望尽量使卡上的余额最少。 
某天,食堂中有n种菜出售,每种菜可购买一次。已知每种菜的价格以及卡上的余额,问最少可使卡上的余额为多少。 

Input

多组数据。对于每组数据: 
第一行为正整数n,表示菜的数量。n<=1000。 
第二行包括n个正整数,表示每种菜的价格。价格不超过50。 
第三行包括一个正整数m,表示卡上的余额。m<=1000。 

n=0表示数据结束。 

Output

对于每组输入,输出一行,包含一个整数,表示卡上可能的最小余额。

Sample Input

1
50
5
10
1 2 3 2 1 1 2 3 2 1
50
0

Sample Output

-45
32

起初我的思路是先把价格升序排序,然后拿余额一个一个减,减到最接近5的时候停止,最后再减去最大的那个价格然后输出,说到底用了贪心的想法,每次减最小的然后结果最接近5,其实不然。

例如这组数据,如果用贪心的话,最多剩10-1-3=6,但明显10-5=5比6小,可见贪心是不行的

这就用到了动态规划里的01背包问题了

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
int a[1100],dp[1100];
int main(){
	int n,m;
	while(cin>>n&&n){
		for(int i=0;i<n;i++)
            cin>>a[i];
		cin>>m;
		if(m<5){
			cout<<m<<endl;
			continue;
		}
		sort(a,a+n);
		memset(dp,0,sizeof(dp));
		for(int i=0;i<n-1;i++)
			for(int j=m-5;j>=a[i];j--)
				dp[j]=max(dp[j],dp[j-a[i]]+a[i]);
		cout<<m-dp[m-5]-a[n-1]<<endl;
	}
	return 0;
}

 

Battle Ships is a new game which is similar to Star Craft. In this game, the enemy builds a defense tower, which has L longevity. The player has a military factory, which can produce N kinds of battle ships. The factory takes ti seconds to produce the i-th battle ship and this battle ship can make the tower loss lilongevity every second when it has been produced. If the longevity of the tower lower than or equal to 0, the player wins. Notice that at each time, the factory can choose only one kind of battle ships to produce or do nothing. And producing more than one battle ships of the same kind is acceptable.

Your job is to find out the minimum time the player should spend to win the game.

Input

There are multiple test cases. 
The first line of each case contains two integers N(1 ≤ N ≤ 30) and L(1 ≤ L ≤ 330), N is the number of the kinds of Battle Ships, L is the longevity of the Defense Tower. Then the following N lines, each line contains two integers t i(1 ≤ t i ≤ 20) and li(1 ≤ li ≤ 330) indicating the produce time and the lethality of the i-th kind Battle Ships.

Output

Output one line for each test case. An integer indicating the minimum time the player should spend to win the game.

Sample Input

1 1
1 1
2 10
1 1
2 5
3 100
1 10
3 20
10 100

Sample Output

2
4
5
这是一道完全背包题目,设dp[j]表示前j秒造成的最大伤害,然后对每一艘船都跑一次背包,实际上对于每艘船,一定是从第一秒开始造,

一个接一个地不停造,dp[j] = max(dp[j], dp[j - t] + c * (j - t)),这样可能好理解一点:c * (j - t)是第一
艘船的贡献,第一艘船造完后还剩j - t的时间,除去第一艘船,j - t时间可以造成的最大伤害就是dp[j - t],再加上
第一艘船的伤害c * (j - t)即可。对于后面的不同种类的船,就在之前跑过的dp上再跑背包就行了。

#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
int dp[366];
int main(){
	int n, l, t, c;
	while(scanf("%d %d", &n, &l) != EOF){
		memset(dp, 0, sizeof(dp));
		for(int i = 1; i <= n; ++i){
			scanf("%d %d", &t, &c);
			for(int j = t; j <= 366; ++j){
				dp[j] = max(dp[j], dp[j - t] + c * (j - t));
			}
		}
		int ans;
		for(int i = 0; i <= 366; ++i){
			if(dp[i] >= l){
				ans = i;
				break;
			}
		}
		printf("%d\n", ans);
	}
}

最后说一下这个周做cf的题目情况吧,明明是一道很简单的题目,当时我不管是用暴力还是dp都做不出来,而事后看题解就发现其实是一道很简单dp题目,这样就让我很困惑,感觉做dp题目就是这样其实并不是不会,思路并不难,可就是不会,总结下来还是题解看多了,而题目又做少了,以后我一定克服这些问题。想要做好无非看自己想不想,需要多花时间,希望自己可以做的更好。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值