理解背包问题
什么是背包问题?
顾名思义,背包问题就是类似于往背包里装东西的问题
分类和解法?
背包问题大概可以分为以下几类:
1.01背包
(一件物品只可取用一次)
解法一 二维数组
解法二 一维数组(倒序)
2.完全背包
(一件物品可取用多次)
解法一 二维数组
解法二 一维数组(正序)
3.多重背包
设法转化为01背包
4.二维费用背包
5.混合三种背包
6.分组背包
7.有依赖的背包
tips.这里着重讲解前三种,后几种类型较为复杂,后续会更新(见背包DP2)
一、Bone Collector
1.题目
Many years ago , in Teddy's hometown there was a man who was called "Bone Collector". This man like to collect varies of bones , such as dog's , cow's , also he went to the grave ...
The bone collector had a big bag with a volume of V ,and along his trip of collecting there are a lot of bones , obviously , different bone has different value and different volume, now given the each bone's value along his trip , can you calculate out the maximum of the total value the bone collector can get ?

++要求++:The first line contain a integer T , the number of cases.
Followed by T cases, each case three lines , the first line contain two integer N,V,(N<=1000,V<=1000) representing the number of bones and the volume of his bag.
And the second line contain N integers representing the value of each bone.
The third line contain N integers representing the volume of each bone.
One integer per line representing the maximum of the total value (this number will be less than 2^31).
++参考译文++ :许多年前,在泰迪的家乡有一个被称为 "骨头收集者" 的人。这个人喜欢收集各种各样的骨头,比如狗的、牛的,他还去了坟墓......
捡骨头的人有一个大袋子,里面有很多骨头,显然不同的骨头有不同的价值和体积,现在给定他一路上每根骨头的价值,你能算出捡骨头的人能得到的总价值的最大值吗?
++要求++ :第一行包含一个整数T,即案例数。
其次是病例,每个病例三行,第一行包含两个整数N,V,代表骨头的数量和他包的体积。
第二行包含代表每个骨骼值的整数。
第三行包含代表每个骨骼体积的整数。
每行一个整数,表示总值的最大值(此数字将小于)。
2.题解
读题可知,这是一个经典的01背包问题
解法一 二维数组
这里,我们可以用一个二维数组DP[N][w]来表示最后一块骨头为当前N位置骨头且背包大小为w时可以装载的最大骨头价值,v[1001]用于存储骨头价值,w[1001]用于存储骨头体积
先初始化DP的值为0,然后分类讨论找出状态转移方程
有两种情况:当我们不取最后一块骨头时,总价值为
cs
dp[i][j]=dp[i-1][j];
取最后一块骨头时
cs
dp[i][j]=dp[i-1][j-w[i-1]]+v[i-1];
我们只需考虑取二者中较大者即可
这便是核心算法
下面是代码实现
完整代码
cs
#include<stdio.h>
#include<string.h>
#include<math.h>
int main(){
int T;
int dp[1001][1001];
int v[1001];
int w[1001];
scanf("%d",&T);
while(T--){
memset(dp,0,sizeof(dp));
int N,V;
scanf("%d %d",&N,&V);
for(int i=0;i<N;i++){
scanf("%d",&v[i]);
}
for(int i=0;i<N;i++){
scanf("%d",&w[i]);
}
for(int i=1;i<=N;i++){
for(int j=w[i-1];j<=V;j++){
dp[i][j]=fmax(dp[i-1][j],dp[i-1][j-w[i-1]]+v[i-1]);
}
}
printf("%d\n",dp[N][V]);
}
return 0;
}
解法二 一维数组
首先定义一维数组dp[j]表示最后一块骨头为i时背包容量为j的最优情况
其他同上,状态转移方程和上面类似,只不过要倒序循环(避免重复选择已装入的骨头)
完整代码如下
完整代码
cs
#include<stdio.h>
#include<string.h>
#include<math.h>
int main(){
int T;
int dp[1001];
int v[1001];
int w[1001];
scanf("%d",&T);
while(T--){
memset(dp,0,sizeof(dp));
int N,V;
scanf("%d %d",&N,&V);
for(int i=0;i<N;i++){
scanf("%d",&v[i]);
}
for(int i=0;i<N;i++){
scanf("%d",&w[i]);
}
for(int i=0;i<N;i++){
for(int j=V;j>=w[i];j--){
dp[j]=fmax(dp[j],dp[j-w[i]]+v[i]);
}
}
printf("%d\n",dp[V]);
}
return 0;
}
二、Piggy-Bank
1.题目
Before ACM can do anything, a budget must be prepared and the necessary financial support obtained. The main income for this action comes from Irreversibly Bound Money (IBM). The idea behind is simple. Whenever some ACM member has any small money, he takes all the coins and throws them into a piggy-bank. You know that this process is irreversible, the coins cannot be removed without breaking the pig. After a sufficiently long time, there should be enough cash in the piggy-bank to pay everything that needs to be paid.
But there is a big problem with piggy-banks. It is not possible to determine how much money is inside. So we might break the pig into pieces only to find out that there is not enough money. Clearly, we want to avoid this unpleasant situation. The only possibility is to weigh the piggy-bank and try to guess how many coins are inside. Assume that we are able to determine the weight of the pig exactly and that we know the weights of all coins of a given currency. Then there is some minimum amount of money in the piggy-bank that we can guarantee. Your task is to find out this worst case and determine the minimum amount of cash inside the piggy-bank. We need your help. No more prematurely broken pigs!
++要求++:
The input consists of T test cases. The number of them (T) is given on the first line of the input file.
Each test case begins with a line containing two integers E and F. They indicate the weight of an empty pig and of the pig filled with coins. Both weights are given in grams. No pig will weigh more than 10 kg, that means 1<=E<=F<=10000.
On the second line of each test case, there is an integer number N(1<=N<=500) that gives the number of various coins used in the given currency.
Following this are exactly N lines, each specifying one coin type. These lines contain two integers each, Pand W(1<=P<=50000,1<=W<=10000). P is the value of the coin in monetary units, W is it's weight in grams.
Print exactly one line of output for each test case. The line must contain the sentence "The minimum amount of money in the piggy-bank is X." where X is the minimum amount of money that can be achieved using coins with the given total weight.
If the weight cannot be reached exactly, print a line "This is impossible.".
++参考译文++ :在 ACM 可以做任何事情之前,必须准备好预算并获得必要的财政支持。这一行动的主要收入来自不可逆转的绑定货币(IBM)。背后的想法很简单。每当一些 ACM 成员有任何小钱时,他就把所有的硬币扔进存钱罐。你知道这个过程是不可逆的,硬币不能在不打破猪的情况下取出。经过足够长的时间,存钱罐里应该有足够的现金来支付所有需要支付的钱。
但是存钱罐有一个很大的问题。不可能确定里面有多少钱。所以我们可能会把猪打碎,却发现里面没有足够的钱。显然,我们想避免这种不愉快的情况。唯一的可能性是称一下存钱罐的重量,试着猜测里面有多少硬币。假设我们能够准确地确定猪的重量,并且我们知道给定货币的所有硬币的重量。那么存钱罐里有一些我们可以保证的最低金额。你的任务是找出最坏的情况,并确定存钱罐里的最低金额现金。我们需要你的帮助。不要再过早打碎猪了!
++要求++ :输入由测试用例组成。它们的数量()在输入文件的第一行给出。
每个测试用例都以包含两个整数和的行开头。它们表示空猪和装满硬币的猪的重量。两种重量都以克为单位。这意味着没有猪的体重会超过公斤。
在每个测试用例的第二行,有一个整数,给出给定货币中使用的各种硬币的数量。
下面是精确的线,每条线指定一种硬币类型。这些线每个包含两个整数,Pand。是硬币的货币单位价值,是它的重量以克为单位。
为每个测试用例精确打印一行输出。该行必须包含句子 "存钱罐中的最小金额为 X"。使用给定总重量的硬币可以实现的最小金额在哪里。
如果重量无法精确达到,请打印一行 "这是不可能的"
2.题解
读题可知,这是一个典型的完全背包问题,即可重复利用硬币
但有一点需要注意,这里增加了一个条件,就是要求能判断能否刚好装满
解法一 二维数组
核心区别 :当选择第i件物品时,01背包从dp[i-1][j-w[i]]转移,而完全背包从dp[i][j-w[i]]转移。
完整代码
cs
#include<stdio.h>
#include<string.h>
#include<limits.h>
int min(int a, int b) {
return a < b ? a : b;
}
int main(){
int T;
scanf("%d", &T);
while(T--){
int E, F;
scanf("%d %d", &E, &F);
int n = F - E; // 需要填充的重量
int N;
scanf("%d", &N);
int v[501], w[501];
for(int i = 1; i <= N; i++){
scanf("%d %d", &v[i], &w[i]);
}
// 二维DP数组,dp[i][j]表示前i种硬币恰好达到重量j的最小价值
int dp[501][10001];
// 初始化
for(int i = 0; i <= N; i++){
for(int j = 0; j <= n; j++){
if(j == 0) {
dp[i][j] = 0; // 重量为0时价值为0
} else {
dp[i][j] = INT_MAX; // 其他状态初始化为不可达
}
}
}
// 动态规划
for(int i = 1; i <= N; i++){
for(int j = w[i]; j <= n; j++){
if(dp[i][j - w[i]] != INT_MAX) {
dp[i][j] = min(dp[i-1][j], dp[i][j - w[i]] + v[i]);// 选第i件物品(关键区别!)
}
}
}
if(dp[N][n] != INT_MAX){
printf("The minimum amount of money in the piggy-bank is %d.\n", dp[N][n]);
} else {
printf("This is impossible.\n");
}
}
return 0;
}
解法二 一维数组(正序遍历)
依旧嵌套两层循环,外层表示骨头数量,内层表示背包容量,dp[j]表示最后一块骨头为i时背包容量为j的最优情况
用一个if判断来确保刚好装下
cs
if(dp[j - w[i]] != INT_MAX)
核心算法如下(由于最后一个必定可取,所以不用分类讨论)
cs
// 完全背包:正序遍历
for(int i = 0; i < N; i++){
for(int j = w[i]; j <= n; j++){
if(dp[j - w[i]] != INT_MAX){ // 确保前一个状态可达
if(dp[j] > dp[j - w[i]] + v[i]){
dp[j] = dp[j - w[i]] + v[i];
}
}
}
}
完整代码
cs
#include<stdio.h>
#include<string.h>
#include<limits.h>
int main(){
int T;
scanf("%d",&T);
while(T--){
int E, F;
scanf("%d %d", &E, &F);
int n = F - E; // 需要填充的重量
int N;
scanf("%d", &N);
int v[501], w[501];
for(int i = 0; i < N; i++){
scanf("%d %d", &v[i], &w[i]);
}
// 使用一维DP数组,dp[j]表示重量为j时的最小价值
int dp[10001];
// 初始化:除了dp[0]=0,其他都设为极大值
dp[0] = 0;
for(int j = 1; j <= n; j++){
dp[j] = INT_MAX; // 表示不可达
}
// 完全背包:正序遍历
for(int i = 0; i < N; i++){
for(int j = w[i]; j <= n; j++){
if(dp[j - w[i]] != INT_MAX){ // 确保前一个状态可达
if(dp[j] > dp[j - w[i]] + v[i]){
dp[j] = dp[j - w[i]] + v[i];
}
}
}
}
if(dp[n] != INT_MAX){
printf("The minimum amount of money in the piggy-bank is %d.\n", dp[n]);
} else {
printf("This is impossible.\n");
}
}
return 0;
}
三、珍惜现在,感恩生活
1.题目
急!灾区的食物依然短缺!
为了挽救灾区同胞的生命,心系灾区同胞的你准备自己采购一些粮食支援灾区,现在假设你一共有资金n元,而市场有m种大米,每种大米都是袋装产品,其价格不等,并且只能整袋购买。
请问:你用有限的资金最多能采购多少公斤粮食呢?
后记:
人生是一个充满了变数的生命过程,天灾、人祸、病痛是我们生命历程中不可预知的威胁。
月有阴晴圆缺,人有旦夕祸福,未来对于我们而言是一个未知数。那么,我们要做的就应该是珍惜现在,感恩生活------
感谢父母,他们给予我们生命,抚养我们成人;
感谢老师,他们授给我们知识,教我们做人
感谢朋友,他们让我们感受到世界的温暖;
感谢对手,他们令我们不断进取、努力。
同样,我们也要感谢痛苦与艰辛带给我们的财富~

++要求++:输入数据首先包含一个正整数 C,表示有 C 组测试用例,每组测试用例的第一行是两个整数 n 和 m (1<=n<=100,1<=m<=100),分别表示经费的金额和大米的种类,然后是 m 行数据,每行包含 3 个数 p,h 和 c (1<=p<=20,1<=h<=200,1<=c<=20),分别表示每袋的价格、每袋的重量以及对应种类大米的袋数
对于每组测试数据,请输出能够购买大米的最多重量,你可以假设经费买不光所有的大米,并且经费你可以不用完
每个实例的输出占一行
2.题解
解法一 暴力遍历
三重循环遍历每一种情况
完整代码
cs
#include<stdio.h>
#include<string.h>
int max(int a, int b) {
return a > b ? a : b;
}
int main() {
int N, V;
scanf("%d %d", &N, &V);
int w[1001], v[1001], s[1001];
for(int i = 1; i <= N; i++) {
scanf("%d %d %d", &w[i], &v[i], &s[i]);
}
int dp[1001] = {0};
// 多重背包:三重循环
for(int i = 1; i <= N; i++) {
for(int j = V; j >= 0; j--) {
for(int k = 0; k <= s[i] && k * w[i] <= j; k++) {
dp[j] = max(dp[j], dp[j - k * w[i]] + k * v[i]);
}
}
}
printf("%d\n", dp[V]);
return 0;
}
解法二 转化
将多重背包转化为01背包(0.2.4.8...)
完整代码
cs
#include<stdio.h>
#include<math.h>
int main(){
int C;
scanf("%d",&C);
for(int i=0;i<C;i++){
int n,m;
scanf("%d %d",&n,&m);
int dp[101] = {0};
int p,h,c;
for(int j=0;j<m;j++){
scanf("%d %d %d",&p,&h,&c);
for(int k=1;k<=c;k*=2){
int c_p=p*k;
int c_h=h*k;
for(int f=n;f>=c_p;f--){
dp[f]=fmax(dp[f],dp[f-c_p]+c_h);
}
c-=k;
}
if(c>0){
int c_p=p*c;
int c_h=h*c;
for(int f=n;f>=c_p;f--){
dp[f]=fmax(dp[f],dp[f-c_p]+c_h);
}
}
}
printf("%d\n",dp[n]);
}
return 0;
}