背包问题解题思路

时间:2022-07-30 18:41:42
背包解题思路
背包问题大部分都是类似模样的状态转移方程dp[j]=dp[j]>(dp[j-t]+v)?dp[j]:(dp[j-t]+v);
普通类型的背包问题只需注意以下情况:
一、对于物品数量:
1、01背包:每件物品只有一件:
这表示物品不可以重复取,于是我们里面的循环可以写成for(j=v;j>=c;j--)这样从最大背包重量到只能装下一个当前物品的最小重量,由于是从后向前推的,设v表示背包的体积,c表示物体的体积而前面并没有装任何当前物品,所以就可以做到每个物品至多装下一件。(外层循环是物品件数)
2、多重背包:
每个物品有有限件(可能是1件或多件,可以多选但不能超过最大件数)
解这类题有两种方法,可行的一种方法是直接把每个物品的N件看成完全相同的N种物品,完成这个操作只需要在外层循环和内层循环之间加上一层while循环即可,就像这样:while(c--)//c即为当前物品的数量
{
for(j=n;j>=p;j--)/*由于物品被当成多种来对待,所以“每件”不能重复选择*/
{
dp[j]=max(dp[j],dp[j-p]+h); //状态转移方程,max()为取最大值函数
}
}

3、完全背包:每个物品有无限件
这表示每件物品都可以随便取,直到背包装不下为止,于是和单纯的只去一件相对,我们里面的循环可以写成for(j=c;j<=v;j++)这样,从只能装下一个当前物品的最小重量到最大背包重量,由于前面较小的背包重量可能已经装过当前物品了,所以后面的就可以装下重复的物品。
4、分组背包:物品分为不同的组,每组最多可以取一件(有的题是每组最少取一件)
这种题的解题技巧也是加一层循环,不过不像多重背包,多加的这一层循环作用为在某一组中找出最优解,在这不好说,会在下面题中说明。

二、对于是否需要装满的要求:
技巧就是不需要装满的情况就把数组初始化为0,需要装满就把数组初始化为负无穷。
我们看到的求最优解的背包问题题目中,事实上有两种不太相同的问法。有的题目要求“恰好装满背包”时的最优解,有的题目则并没有要求必须把背包装满。一种区别这两种问法的实现问题是在初始化的时候有所不同。
如果是第一种问法,要求恰好装满背包,那么在初始化时除了F[0]为0,其他F[1..V]均设为负无穷,这样就可以保证最终得到的F[V]是一种恰好装满背包的最优解。
如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将F[0….V]全部设为0。
这是为什么呢?可以这样理解:初始化的F数组事实上就是在没有任何物品可以放入背包时的合法状态。如果要求背包恰好装满,那么此时只有容量为0的背包可以在什么也不装且价值为0的情况下被”恰好装满”,其他容量的背包均没有合法的解,属于未定义的状态,应该被赋值为负无穷了。如果背包并非必须被装满,那么任何容量的背包都有一个合法解”什么都不装“,这个解的价值为0,所以初始化时状态的值也就全部为0了。
这个小技巧完全可以推广到其他类型的背包问题,后面也就不再对进行状态转移之前的初始化进行讲解。剩下的就是一些背包问题的变形以及扩展,那些题就需要随机应变了,下面会有例题。下面上例题:
采药问题
题目描述
医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,
每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。
如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。” 如果你是辰辰,你能完成这个任务吗?
输入
输入的第一行有两个整数T(1 < = T < = 1000)和M(1 < = M < = 100),用一个空格隔开,
T代表总共能够用来采药的时间,M代表山洞里的草药的数目。接下来的M行每行包括两个在1到100之间(包括1和100)的整数,
分别表示采摘某株草药的时间和这株草药的价值。
输出
输出包括一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。
样例输入
70 3
71 100
69 1
1 2
样例输出
3
提示
对于30%的数据,M < = 10;对于全部的数据,M < = 100。

代码:
#include<stdio.h>
#include<string.h>
int dp[1001];
int main()
{
int time,m,i,j,t,v;
scanf("%d%d",&time,&m);
memset(dp,0,sizeof(dp));
for(i=1;i<=m;i++)
{
scanf("%d%d",&t,&v);
for(j=time;j>=t;j--)
dp[j]=dp[j]>(dp[j-t]+v)?dp[j]:(dp[j-t]+v);
}
printf("%d\n",dp[time]);
return 0;
}

NYOJ 289 苹果
题目信息:
苹果
时间限制:3000 ms | 内存限制:65535 KB
难度:3
描述
ctest有n个苹果,要将它放入容量为v的背包。给出第i个苹果的大小和价钱,求出能放入背包的苹果的总价钱最大值。
输入
有多组测试数据,每组测试数据第一行为2个正整数,分别代表苹果的个数n和背包的容量v,n、v同时为0时结束测试,此时不输出。接下来的n行,每行2个正整数,用空格隔开,分别代表苹果的大小c和价钱w。所有输入数字的范围大于等于0,小于等于1000。
输出
对每组测试数据输出一个整数,代表能放入背包的苹果的总价值。
样例输入
3 3
1 1
2 1
3 1
0 0
样例输出
2
代码:
#include <stdio.h>
#include <string.h>
int main()
{
int n,v,c,w,i,j;
int dp[1005];
while(scanf("%d%d",&n,&v)!=EOF&&!(n==0&&v==0))
{
memset(dp,0,sizeof(dp));
for(i=0;i<n;i++)
{
scanf("%d%d",&c,&w);
for(j=v;j>=c;j--)
dp[j]=dp[j]>(dp[j-c]+w)?dp[j]:(dp[j-c]+w);
}
printf("%d\n",dp[v]);
}
return 0;
}

NYOJ 311 完全背包
题目信息:
完全背包
时间限制:3000 ms | 内存限制:65535 KB
难度:4
描述
直接说题意,完全背包定义有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的体积是c,价值是w。求解将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大。本题要求是背包恰好装满背包时,求出最大价值总和是多少。如果不能恰好装满背包,输出NO
输入
第一行: N 表示有多少组测试数据(N<7)。
接下来每组测试数据的第一行有两个整数M,V。 M表示物品种类的数目,V表示背包的总容量。(0<M<=2000,0<V<=50000)
接下来的M行每行有两个整数c,w分别表示每种物品的重量和价值(0<c<100000,0<w<100000)
输出
对应每组测试数据输出结果(如果能恰好装满背包,输出装满背包时背包内物品的最大价值总和。 如果不能恰好装满背包,输出NO)
样例输入
2
1 5
2 2
2 5
2 2
5 1
样例输出
NO
1
解题思路:
每种物品无限用,所以循环从前向后递推。此题要求必须装满,所以初始化时把数组初始化为负无穷大,最后用来筛选排除掉还是负数的(由于只有op[0]=0,op数组的其它值都是负无穷大,所以只有剩余空间为0时op[j]最后的值op[j]=op[j]>op[j-s[i].c]+s[i].w?op[j]:op[j-s[i].c]+s[i].w;才可能为正数,如果还为负数则表示不能装满)。

代码:
#include <stdio.h>
#include <string.h>
int op[50005];
struct node{
int c;//重量
int w;//价值
}s[2002];
int main()
{
int N,M,V,i,j,t;
scanf("%d",&N);
while(N--)
{
memset(op,-0x3f,sizeof(op));
op[0]=0;
scanf("%d%d",&M,&V);
for(i=0;i<M;i++)
scanf("%d%d",&s[i].c,&s[i].w);
for(i=0;i<M;i++)
{
for(j=s[i].c;j<=V;j++)
{
op[j]=op[j]>op[j-s[i].c]+s[i].w?op[j]:op[j-s[i].c]+s[i].w;
}
}
if(op[V]<0)
printf("NO\n");
else
printf("%d\n",op[V]);
}
return 0;
}

HDU 2191 多重背包问题
题目信息:
Problem Description
急!灾区的食物依然短缺!
为了挽救灾区同胞的生命,心系灾区同胞的你准备自己采购一些粮食支援灾区,现在假设你一共有资金n元,而市场有m种大米,每种大米都是袋装产品,其价格不等,并且只能整袋购买。
请问:你用有限的资金最多能采购多少公斤粮食呢?
Input
输入数据首先包含一个正整数C,表示有C组测试用例,每组测试用例的第一行是两个整数n和 m(1<=n<=100, 1<=m<=100),分别表示经费的金额和大米的种类,然后是m行数据,每行包含3个数p,h和c(1<=p<=20,1& amp;lt;=h<=200,1<=c<=20),分别表示每袋的价格、每袋的重量以及对应种类大米的袋数。

Output
对于每组测试数据,请输出能够购买大米的最多重量,你可以假设经费买不光所有的大米,并且经费你可以不用完。每个实例的输出占一行。

Sample Input
1
8 2
2 100 4
4 100 2

Sample Output
400
解题思路:
多重背包即每种物品有有限多个可选择的背包问题,其解法和普通的背包问题基本一样,只需把每种物品看成多件即可。
代码部分:
#include <stdio.h>
#include <string.h>
int max(int a,int b)
{
return a>b?a:b;
}
int main()
{
int t,m,n,i,j,k,p,h,c;
int dp[500];
scanf("%d",&t);
while(t--)
{
scanf("%d %d",&n,&m);
memset(dp,0,sizeof(dp));
for(i=0;i<m;i++)
{
scanf("%d %d %d",&p,&h,&c);
while(c--)
{
for(j=n;j>=p;j--)
{
dp[j]=max(dp[j],dp[j-p]+h);
}
}
}
printf("%d\n",dp[n]);
}
return 0;
}



HDU 1712 分组背包问题
题目信息:
ACboy needs your help
Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 5146 Accepted Submission(s): 2780
Problem Description
ACboy has N courses this term, and he plans to spend at most M days on study.Of course,the profit he will gain from different course depending on the days he spend on it.How to arrange the M days for the N courses to maximize the profit?

Input
The input consists of multiple data sets. A data set starts with a line containing two positive integers N and M, N is the number of courses, M is the days ACboy has.
Next follow a matrix A[i][j], (1<=i<=N<=100,1<=j<=M<=100).A[i][j] indicates if ACboy spend j days on ith course he will get profit of value A[i][j].
N = 0 and M = 0 ends the input.

Output
For each data set, your program should output a line which contains the number of the max profit ACboy will gain.

Sample Input
2 2
1 2
1 3
2 2
2 1
2 1
2 3
3 2 1
3 2 1
0 0

Sample Output
3
4
6
题目大意:
一个学生用M天的时间复习N门课程,每门课程花费不同的天数,有不同的收获。问如何安排这M天,使得收获最大。
解题思路:
分组背包即物品分为多组,每一组的各个物品最多只能取一个。解题方法即在循环最里面再嵌套一个循环,查找每组中价值最大的物品。
代码部分:
#include <stdio.h>
#include <string.h>
int main()
{
int N,M,i,j,k;
int dp[105],value[105];
while(scanf("%d %d",&N,&M)&&M||N)
{
memset(dp,0,sizeof(dp));
memset(value,0,sizeof(value));
for(i=1;i<=N;i++)/*外面的这层循环为组数,在这一题中为课程数*/
{
for(j=1;j<=M;j++)/*这里面输入的是每一组物品的信息,这一题中为当前课程花费多少天数可得到的收获,如输入1 3 2表示当前课程学1天收获为1,学两天收获3,学3天收获2*/
{
scanf("%d",&value[j]);
}
for(j=M;j>0;j--)/*天数从大到小循环,保证不会出现某一门课程既总共复习了一天,又总共复习了两天这样的情况发生*/
{
for(k=j;k>0;k--)/*当前这一组物品在当前天数情况下可选取的最优解*/
dp[j]=dp[j]>dp[j-k]+value[k]?dp[j]:dp[j-k]+value[k];
}
}
printf("%d\n",dp[M]);
}
return 0;
}



下面就是一些背包问题的扩展及变形,需要把思路转换一下:
NYOJ 720 项目安排
题目信息:
项目安排
时间限制:3000 ms | 内存限制:65535 KB
难度:4
描述
小 明每天都在开源社区上做项目,假设每天他都有很多项目可以选,其中每个项目都有一个开始时间和截止时间,假设做完每个项目后,拿到报酬都是不同的。由于小 明马上就要硕士毕业了,面临着买房、买车、给女友买各种包包的鸭梨,但是他的钱包却空空如也,他需要足够的money来充实钱包。万能的网友麻烦你来帮帮 小明,如何在最短时间内安排自己手中的项目才能保证赚钱最多(注意:做项目的时候,项目不能并行,即两个项目之间不能有时间重叠,但是一个项目刚结束,就 可以立即做另一个项目,即项目起止时间点可以重叠)。
输入
输入可能包含多个测试样例。
对于每个测试案例,输入的第一行是一个整数n(1<=n<=5000):代表小明手中的项目个数。
接下来共有n行,每行有3个整数st、ed、val,分别表示项目的开始、截至时间和项目的报酬,相邻两数之间用空格隔开。
st、ed、value取值均在32位有符号整数(int)的范围内,输入数据保证所有数据的value总和也在int范围内。
输出
对应每个测试案例,输出小明可以获得的最大报酬。
样例输入
3
1 3 6
4 8 9
2 5 16
4
1 14 10
5 20 15
15 20 8
18 22 12
样例输出
16
22
提示
上传时数据加强,项目起始时间和终止时间可能相同(其他oj可能无此情况)
解题思路:
乍一看这题有点贪心算法的味道,其实不是。这仍然是一道动态规划的背包问题,而状态转移方程需要去找到结束时间在当前项目开始时间之前的项目,那时 的最大报酬加上当前项目的报酬之和去和上一个项目的报酬相比较取最大值。完成这样的操作则必须需要排序,因为这里需要去找到结束时间在当前项目开始时间之前的项 目,所以需要按项目结束时间从小到大排序,相同则按项目开始时间从小到大排序。
代码部分:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int dp[5005];
struct Project
{
int st;
int ed;
int value;
}item[5005];
int find(int k)
{
int head=1,arrow,tail=k;
while(tail>head)
{
arrow=(head+tail)/2;
if(item[arrow].ed<=item[k].st)
head=arrow+1;
else
tail=arrow;
}
return tail-1;
}
int cmp(const void *a,const void *b)
{
Project *c=(Project *)a;
Project *d=(Project *)b;
if(c->ed!=d->ed)
return c->ed-d->ed;
return c->st-d->st;
}
int main()
{
int n,i;
while(~scanf("%d",&n))
{
for(i=1;i<=n;i++)
scanf("%d%d%d",&item[i].st,&item[i].ed,&item[i].value);
qsort(&item[1],n,sizeof(item[1]),cmp);
memset(dp,0,sizeof(dp));
for(i=1;i<=n;i++)
dp[i]=dp[i-1]>(dp[find(i)]+item[i].value)?dp[i-1]:(dp[find(i)]+item[i].value);
printf("%d\n",dp[n]);
}
return 0;
}



NYOJ 613 免费馅饼
题目信息:
免费馅饼
时间限制:1000 ms | 内存限制:65535 KB
难度:3
描述
都说天上不会掉馅饼,但有一天gameboy正走在回家的小 径上,忽然天上掉下大把大把的馅饼。说来gameboy的人品实在是太好了,这馅饼别处都不掉,就掉落在他身旁的10米范围内。馅饼如果掉在了地上当然就 不能吃了,所以gameboy马上卸下身上的背包去接。但由于小径两侧都不能站人,所以他只能在小径上接。由于gameboy平时老呆在房间里玩游戏,虽 然在游戏中是个身手敏捷的高手,但在现实中运动神经特别迟钝,每秒种只有在移动不超过一米的范围内接住坠落的馅饼。现在给这条小径如图标上坐标:


为了使问题简化,假设在接下来的一段时间里,馅饼都掉落在0-10这11个位置。开始时gameboy站在5这个位置,因此在第一秒,他只能接到4,5,6这三个位置中其中一个位置上的馅饼。问gameboy最多可能接到多少个馅饼?(假设他的背包可以容纳无穷多个馅饼)
输入
输入数据有多组。每组数据的第一行为以正整数 n(0<n<100000),表示有n个馅饼掉在这条小径上。在结下来的n行中,每行有两个整数 x,T(0<T<100000),表示在第T秒有一个馅饼掉在x点上。同一秒钟在同一点上可能掉下多个馅饼。n=0时输入结束。
输出
每一组输入数据对应一行输出。输出一个整数m,表示gameboy最多可能接到m个馅饼。
提示:本题的输入数据量比较大,建议用scanf读入,用cin可能会超时。
样例输入
6
5 1
4 1
6 1
7 2
7 2
8 3
0
样例输出
4
解题思路:
动态规划的背包问题的变形,把馅饼从上向下落想象成从下向上落,只需要建立数组输入每个馅饼的位置(坐标),从下向上每层循环,每个dp[i][j]找dp[i+1][j-1]、dp[i+1][j]、dp[i+1][j+1]中的最大值就行了,只需要注意边界的问题就可以了,我处理边界的方法是加一圈边,即数组左边留一列初始化为0(防止j-1溢出),右边、下边也多一列(一行)初始化为0,这个定义数组时定大点就OK啦,然后一层层往上加,最后输出最上层数组最中间的值,就是一开始站的位置,那里的值就是能接的馅饼最大数量。
代码部分:
#include <stdio.h>
#include <string.h>
int dp[100005][13];
int Max(int a,int b,int c)
{
if(a<b)
a=b;
if(a<c)
a=c;
return a;
}
int main()
{
int n,x,t,i,j,maxtime;
while(scanf("%d",&n)!=EOF&&n!=0)
{
memset(dp,0,sizeof(dp));
maxtime=0;
for(i=0;i<n;i++)
{
scanf("%d%d",&x,&t);
dp[t][x+1]++;/*某个位置有多少个馅饼*/
if(t>maxtime)/*找最大行数*/
maxtime=t;
}
for(i=maxtime-1;i>=0;i--)
{
for(j=1;j<=11;j++)
{
dp[i][j]=Max(dp[i+1][j-1],dp[i+1][j],dp[i+1][j+1])+dp[i][j];
}
printf("%d\n",dp[0][6]);
}
}
return 0;
}


NYOJ 860 又见01背包
题目信息:
又见01背包
时间限制:1000 ms | 内存限制:65535 KB
难度:3
描述
有n个重量和价值分别为wi 和 vi 的 物品,从这些物品中选择总重量不超过 W
的物品,求所有挑选方案中物品价值总和的最大值。
  1 <= n <=100
  1 <= wi <= 10^7
  1 <= vi <= 100
  1 <= W <= 10^9
输入
多组测试数据。
每组测试数据第一行输入,n 和 W ,接下来有n行,每行输入两个数,代表第i个物品的wi 和 vi。
输出
满足题意的最大价值,每组测试数据占一行。
样例输入
4 5
2 3
1 2
3 4
2 2
样例输出
7

解题思路:
题目为普通的01背包问题,只是数据有点大额,开不了那么大的数组。但是价值不算大,所以我们可以换个思路,从价值着手做,求价值一定时的最小物品重量,然后从大到小找第一个满足背包大小的状态。代码部分:
#include <stdio.h>
#include <string.h>
long long dp[10005],w[105],t;
int v[105],n,i,j,sumv;
int main()
{
while(scanf("%d%lld",&n,&t)!=EOF)
{
memset(dp,0x3f,sizeof(dp));
dp[0]=0;
sumv=0;
for(i=1;i<=n;i++)
{
scanf("%lld%d",&w[i],&v[i]);
sumv+=v[i];/*所有物品的价值总和,用来当做“背包重量”循环用*/
}
for(i=1;i<=n;i++)
for(j=sumv;j>=v[i];j--)
dp[j]=dp[j]<(dp[j-v[i]]+w[i])?dp[j]:(dp[j-v[i]]+w[i]);/*这里因为需要寻找当前价值的最小背包重量,所以需要在其中找小的*/
for(i=sumv;i>=0;i--)
if(dp[i]<=t)/*价值从大到小、找到第一个能被背包装下的重量*/
break;
printf("%d\n",i);
}
return 0;
}