题目内容为《背包九讲》内所提及的问题,全部由经典例题改编而来
A : 买零食
Time Limit:1.000 Sec Memory Limit:128 MiB
Description
贪吃的大三学长ZHB想要买一箱零食
已知他有一个箱子容量为\(V\)(正整数,\(0 \leqslant V \leqslant 200000\)),商店有\(n\)件零食(\(0<n\leqslant 30\),每件零食有一个体积(正整数))。
要求\(n\)件零食中,任取若干件装入箱内,ZHB学长买走零食后剩余空间最少。
Input
\(1\)个整数,表示箱子容量
\(1\)个整数,表示有\(n\)个物品
接下来\(n\)行,分别表示这\(n\)个物品的各自体积
Output
\(1\)个整数,表示箱子剩余空间。
Sample Input
24
6
8
3
12
7
9
7
Sample Output
0
PZ's Solution
1.经典的\(01\)背包,只是物品的价值与重量相等;
2.状态转移方程为\(f[j]=max(f[j],f[j-x]+x)\),其中\(x\)为零食的体积;
2.剩余空间即为\(V-f[V]\);
- TAG:01背包
PZ.cpp
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int V,n,x,f[20005];
int main(){
scanf("%d %d",&V,&n);
for(int i=1;i<=n;++i){
scanf("%d",&x);
for(int j=V;j>=x;--j)
f[j]=max(f[j],f[j-x]+x);
}
printf("%d",V-f[V]);
return 0;
}
B : 采药
Time Limit:1.000 Sec Memory Limit:128 MiB
Description
辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”如果你是辰辰,你能完成这个任务吗?
Input
第一行有2个整数\(T(1 \leqslant T \leqslant 1000)\)和\(M(1 \leqslant M \leqslant 100)\),用一个空格隔开,\(T\)代表总共能够用来采药的时间,\(M\)代表山洞里的草药的数目。
接下来的\(M\)行每行包括两个在\(1\)到\(100\)之间(包括\(1\)和\(100\))的整数,分别表示采摘某株草药的时间和这株草药的价值。
Output
\(1\)个整数,表示在规定的时间内可以采到的草药的最大总价值。
Sample Input
70 3
71 100
69 1
1 2
Sample Output
3
More Info
这是一道非常标准的\(01\)背包问题
PZ's Solution
1.\(01\)背包,状态转移方程为\(f[i]=max(f[i],f[i-t]+w)\)
- TAG:01背包
PZ.cpp
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int T,M,t,w,f[1005];
int main(){
scanf("%d %d",&T,&M);
for(int i=1;i<=M;++i){
scanf("%d %d",&t,&w);
for(int j=T;j>=t;--j)
f[j]=max(f[j],f[j-t]+w);
}
printf("%d",f[T]);
return 0;
}
C : 燕山采药
Time Limit:1.000 Sec Memory Limit:128 MiB
Description
ZHB已经是一个老年人了,他经常需要一些中药补补身体,但无奈于空空的钱包,他只能自己到燕山上去采药。碰巧的是,他找到了一个山洞,山洞里全部都是草药。ZHB很想把它们全部采走,但受制于自己身体,他只能在有限的时间内去采。已知每一株药草都有对应的药效和采摘时间,请你设计一个算法帮助ZHB,让TA在有限的时间内采摘的药草药效总和最大。
Input
输入第一行有两个整数\(T(1 \leqslant T \leqslant 100000)\)和\(M(1 \leqslant M \leqslant 10000)\),用一个空格隔开,\(T\)代表总共能够用来采药的时间,\(M\)代表山洞里的草药的数目。接下来的\(M\)行每行包括两个在\(1\)到\(10000\)之间(包括\(1\)和\(10000\))的整数,分别表示采摘某种草药的时间和这种草药的药效。
Output
输出一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总药效。
Sample Input
70 3
71 100
69 1
1 2
Sample Output
140
More Info
本题基于采药改编,有些许不同,请注意
PZ's Solution
1.题面表达其实有些问题,但从样例应该能看出来,这是一个完全背包;
2.完全背包的状态转移方程与\(01\)背包相同,只是循环顺序的问题;
3.有状态转移方程\(f[i]=max(f[i],f[i-t]+w)\)
ps.不知道为啥C++的程序挂了,蜜汁问题~
- TAG:完全背包
PZ.c
#include<stdio.h>
int T,M,t,w,f[100005];
int main(){
scanf("%d %d",&T,&M);
for(int i=1;i<=M;++i){
scanf("%d %d",&t,&w);
for(int j=t;j<=T;++j)
if(f[j-t]+w>f[j]) f[j]=f[j-t]+w;
}
printf("%d",f[T]);
return 0;
}
D : 卖书
Time Limit:1.000 Sec Memory Limit:128 MiB
Description
“卖书了,卖书了!亏本大甩卖啊!”
大书商小明非常想把手上的\(M\)本书卖给旗下\(N\)家加盟店。已知这些店收购这些书会给小明带来一定的利润,请你设计一个算法,帮助大书商小明获得最大盈利。
已知\(1\leqslant M\leqslant 15,1\leqslant N \leqslant 10\),小明可以任意数量的书卖给任意加盟店,但总数不超过小明持有的数\(M\)。
Input
第一行有两个数,第一个数是小明持有的书本数\(N\),第二个数是加盟店店数\(M\)。
接下来是一个\(N*M\)的矩阵,表明了第\(I\)个加盟店卖得\(J\)台书本的盈利。
Output
第\(1\)行为最大盈利值
第\(2\)到第\(n\)行为第\(x\)个加盟店分\(y\)本所产生的利润
并使输出的答案字典序最小
Sample Input
3 3
50 60 30
30 20 80
60 80 50
Sample Output
140
1 1
2 1
3 1
More Info
背包问题经典解法
Solution
题解借鉴自sycstudio的CJOJ 1131 机器分配 / Luogu 2066 机器分配 (动态规划)
1.背包问题,设\(f[i][j]\)代表前\(i\)个加盟店,总共卖出\(j\)本书获得的最大利润;
2.有状态转移方程\(f[i][j]=max(f[i][j],f[i-1][j-k]+a[i][k])\),其中\(a[i][k]\)代表第\(i\)个加盟店卖出\(k\)本书的利润;
3.之后难点在于输出字典序最小的方案(题面意思是要输出符合方案的\(x\)和\(y\));
4.这里借鉴了其他博客的做法,使用了一个递归函数,倒退求出了字典序最小的解;
5.细节见代码;
- TAG:动态规划;背包
std.cpp
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m,a[20][20],f[20][20];
void printans(int N,int M){
//N代表第N个加盟店,M代表还有M本书
if(N==0) return;
for(int i=0;i<=M;++i) //i代表这个加盟店拿走一部份书后,剩余的书本数
//这样,就能保证下一个加盟店拿的更少,保证了字典序方案
if(f[N][M]==f[N-1][i]+a[N][M-i]){//如果当前的i可以满足最优的条件,说明这种方案符合答案
printans(N-1,i);
printf("%d %d\n",N,M-i);
break;
}
}
int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
scanf("%d",&a[i][j]);
for(int i=1;i<=n;++i)
for(int j=0;j<=m;++j) //因为有的公司可以一本书不要,所有循环从0开始
for(int k=0;k<=j;++k)
f[i][j]=max(f[i][j],f[i-1][k]+a[i][j-k]);
printf("%d\n",f[n][m]);
printans(n,m);//打印方案(注意从后往前递归)
return 0;
}
E : 买零食(二)
Time Limit:1.000 Sec Memory Limit:128 MiB
Description
今天阳光正好,WWN学姐想去超市买几包零食送给正在敲代码的小萌新。
超市里的商品琳琅满目,大大小小价格不一的商品让热爱思考的学姐想到了一个有趣的问题:
他手上的袋子可以装下体积为\(V\)的零食,而每件商品都有自己所对应的价格和体积,那么她这次购物最大花销会是多少呢?
但是聪明的学姐很快就想出了答案,这让她感觉很没有挑战性...于是学姐又继续思考:
那么我这次购物第\(K\)大花销会是多少呢?
这次可让学姐把自己给难住了
那么就请你设计一个算法帮助学姐解决这个问题吧
Input
第一行包含一个整数\(T\),代表测试数据组数
对接下来的\(T\)组数据,每组数据有三行
第一行有三个整数\(N,V,K(N \leqslant 100 , V \leqslant 1000 , K \leqslant 30)\),分别代表零食数量、学姐手上袋子的体积和所要求的第\(K\)大花销
第二行有\(N\)个整数,分别代表每件零食的价格
第三行有\(N\)个整数,分别代表每件零食的体积
Output
\(T\)行整数,分别是每组数据的解(解的大小将小于\(2^{31}\))
Sample Input
3
5 10 2
1 2 3 4 5
5 4 3 2 1
5 10 12
1 2 3 4 5
5 4 3 2 1
5 10 16
1 2 3 4 5
5 4 3 2 1
Sample Output
12
2
0
More Info
可以参考背包九讲里面的思路,改自HDUOJ2639
Solution
思路借鉴了神回的HDU - 2639 Bone Collector II
1.如果只求最大,此题即为\(01\)背包的模板;
2.现在需求第\(K\)大,则设\(f[i][k]\)为体积为\(i\)时的第\(k\)大解;
3.按照原本的状态转移方程\(f[i]=max(f[i],f[i-v[j]]+w[j])\),只需要求出当前\(i\)体积的所有可能情况,并将这些情况的值进行排序,将前\(k\)大(不重复)的值装入相应的\(f[i][k]\)即可;
ps.C++又蜜汁挂了,这里直接写了一个快排代替排序;
- TAG:01背包
std.c
#include<stdio.h>
int T,n,v,k,w[5005],V[5005],F[1005][50],f[5005],cnt,fcnt;
void quicksort(int l,int r)
{
int i=l,j=r,mid=f[(i+j)/2];
while(i<j)
{
while(f[j]<mid) j--;
while(f[i]>mid) i++;
if(i<=j)
{
int t=f[i];
f[i]=f[j];
f[j]=t;
i++;
j--;
}
}
if(l<j) quicksort(l,j);
if(i<r) quicksort(i,r);
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d %d %d",&n,&v,&k);
for(int i=1;i<=n;++i)
scanf("%d",&w[i]);
for(int i=1;i<=n;++i)
scanf("%d",&V[i]);
for(int i=0;i<=v;++i)
for(int j=0;j<=k;++j)
F[i][j]=0;
for(int i=1;i<=n;++i){
for(int j=v;j>=V[i];--j){
fcnt=cnt=0;
for(int K=1;K<=k;++K){
f[++cnt]=F[j][K];
f[++cnt]=F[j-V[i]][K]+w[i];
}
//求出所有可能的情况
quicksort(1,cnt);//从大到小排序
for(int K=1;K<=cnt;++K){
if(fcnt==k) break;
if(f[K]==f[K+1]) continue;
F[j][++fcnt]=f[K];
}
//将不重复的前k大的值装入相应的F[j][K]即可
}
}
printf("%d\n",F[v][k]);
}
return 0;
}