0/1背包:
时间限制: 1 Sec 内存限制: 128 MB
提交: 111 解决: 58
[提交][状态][讨论版][命题人: cbc]
一个旅行者有一个最多能用m公斤的背包,现在有n件物品,它们的重量分别是W1,W2,...,Wn,它们的价值分别为C1,C2,...,Cn.若每种物品只有一件求旅行者能获得最大总价值。
第1行:两个整数,M(背包容量,M<=200)和N(物品数量,N<=30);
第2..N+1行:每行二个整数Wi,Ci,表示每个物品的重量和价值。
仅一行,一个数,表示最大总价值。
10 4
2 1
3 3
4 5
7 9
12
//方法一:二维数组
#include
#include
using namespace std;
int main()
{
int M,N,w[35],c[35],f[35][205];//f集合表示前i个物品,总重量不超过M的旅行者能获得的最大总价值
int i,j;
cin>>M>>N;
for(i=1;i<=N;i++)
scanf("%d %d",&w[i],&c[i]);
for(i=1;i<=N;i++)
{
for(j=1;j<=M;j++)
{
if(j>=w[i])
f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+c[i]);
else
f[i][j]=f[i-1][j];
}
}
printf("%d\n",f[N][M]);
return 0;
}
//方法二:一维数组
#include
#include
using namespace std;
int main()
{
int M,N,w[35],c[35];
int f[205]={0};//f集合表示总重量不超过M的旅行者能获得的最大总价值
//f初值应为0
int i,j;
cin>>M>>N;
for(i=1;i<=N;i++)
scanf("%d %d",&w[i],&c[i]);
for(i=1;i<=N;i++)
{
for(j=M;j>=w[i];j++)
{
f[j]=max(f[j],f[j-w[i]]+c[i]);
}
}
printf("%d\n",f[M]);
return 0;
}
完全背包问题:
时间限制: 1 Sec 内存限制: 128 MB
提交: 98 解决: 60
[提交][状态][讨论版][命题人: cbc]
设有n种物品,每种物品有一个重量及一个价值。但每种物品的数量是无限的,同时有一个背包,最大载重量为M,今从n种物品中选取若干件(同一种物品可以多次选取),使其重量的和小于等于M,而价值的和为最大。
第一行:两个整数,M(背包容量,M<=200)和N(物品数量,N<=30);
第2..N+1行:每行二个整数Wi,Ci,表示每个物品的重量和价值。
仅一行,一个数,表示最大总价值。
10 4
2 1
3 3
4 5
7 9
max=12
#include
#include
using namespace std;
int main()
{
int M,N,w[35],c[35],f[205]={0};
int i,j;
cin>>M>>N;
for(i=1;i<=N;i++)
scanf("%d %d",&w[i],&c[i]);
for(i=1;i<=N;i++)
{
for(j=w[i];j<=M;j++)
{
f[j]=max(f[j],f[j-w[i]]+c[i]);
}
}
printf("max=%d\n",f[M]);
return 0;
}
这个算法使用一维数组,先看伪代码: for i=1..N for v=0..V f[v]=max{f[v],f[v-w[i]]+c[i]}; 你会发现,这个伪代码与01背包问题的伪代码只有v的循环次序不同而 已。为什么这样一改就可行呢?首先想想为什么01背包问题中要按照v=V..0 的逆序来循环。这是因为要保证第i次循环中的状态f[i][v]是由状态f[i-1][vw[i]]递推而来。换句话说,这正是为了保证每件物品只选一次,保证在考 虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的 子结果f[i-1][v-w[i]]。而现在完全背包的特点恰是每种物品可选无限件,所 以在考虑“加选一件第i种物品”这种策略时,却正需要一个可能已选入第i 种物品的子结果f[i][v-w[i]],所以就可以并且必须采用v= 0..V的顺序循环。 这就是这个简单的程序为何成立的道理。 这个算法也可以以另外的思路得出。例如,基本思路中的状态转移方程 可以等价地变形成这种形式: f[i][v]=max{f[i-1][v],f[i][v-w[i]]+c[i]},将这个方程用一维数组实现,便得到了 上面的伪代码。
多重背包问题:
时间限制: 1 Sec 内存限制: 128 MB
提交: 110 解决: 54
[提交][状态][讨论版][命题人: cbc]
为了庆贺班级在校运动会上取得全校第一名成绩,班主任决定开一场庆功会,为此拨款购买奖品犒劳运动
员。期望拨款金额能购买最大价值的奖品,可以补充他们的精力和体力。
第一行二个数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
using namespace std;
int v[6002], w[6002], s[6002];
int f[6002];
int n, m;
int max(int x,int y) {
if (x < y) return y;
else return x;
}
int main()
{
scanf("%d%d",&n,&m);
for (int i = 1; i <= n; i++)
scanf("%d%d%d",&v[i],&w[i],&s[i]);
for (int i = 1; i <= n; i++)
for (int j = m; j >= 0; j--)
for (int k = 0; k <= s[i]; k++)
{
if (j-k*v[i]<0) break;
f[j] = max(f[j],f[j-k*v[i]]+k*w[i]);
}
printf("%d",f[m]);
return 0;
}
//【解法二】进行二进制优化,转换为01背包
//【参考程序】
#include
int v[10001],w[10001];
int f[6001];
int n,m,n1;
int max(int a,int b){
return a>b?a:b; //这句话等于:if (a>b) return a; else return b;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
int x,y,s,t=1;
scanf("%d%d%d",&x,&y,&s);
while (s>=t)
{
v[++n1]=x*t; //相当于n1++; v[n1]=x*t;
w[n1]=y*t;
s-=t;
t*=2;
}
v[++n1]=x*s;
w[n1]=y*s; //把s以2的指数分堆:1,2,4,…,2^(k-1),s-2^k+1,
}
for(int i=1;i<=n1;i++)
for(int j=m;j>=v[i];j--)
f[j]=max(f[j],f[j-v[i]]+w[i]);
printf("%d\n",f[m]);
return 0;
}
基本算法: 这题目和完全背包问题很类似。基本的方程只 需将完全背包问题的方程略微一改即可,因为对于第 i种物品有n[i]+1种策略:取0件,取1件……取n[i]件 。令f[i][v]表示前i种物品恰放入一个容量为v的背包 的最大权值,则:f[i][v]=max{f[i-1][v-k*w[i]]+ k*c[i]|0<=k<=n[i]}。复杂度是O(V*∑n[i])。
转化为01背包问题 另一种好想好写的基本方法是转化为01背包求解:把第i种物品换成n[i]件01背包 中的物品,则得到了物品数为∑n[i]的01背包问题,直接求解,复杂度仍然是 O(V*∑n[i])。 但是我们期望将它转化为01背包问题之后能够像完全背包一样降低复杂度。仍 然考虑二进制的思想,我们考虑把第i种物品换成若干件物品,使得原问题中第i种物 品可取的每种策略——取0..n[i]件——均能等价于取若干件代换以后的物品。另外, 取超过n[i]件的策略必不能出现。 方法是:将第i种物品分成若干件物品,其中每件物品有一个系数,这件物品的费 用和价值均是原来的费用和价值乘以这个系数。使这些系数分别为 1,2,4,...,2^(k1),n[i]-2^k+1,且k是满足n[i]-2^k+1>0的最大整数(注意:这些系数已经可以组合出 1~n[i]内的所有数字)。例如,如果n[i]为13,就将这种物品分成系数分别为1,2,4,6的四 件物品。 分成的这几件物品的系数和为n[i],表明不可能取多于n[i]件的第i种物品。另外这 种方法也能保证对于0..n[i]间的每一个整数,均可以用若干个系数的和表示,这个证 明可以分0..2^k-1和2^k..n[i]两段来分别讨论得出,并不难,希望你自己思考尝试一 下。 这样就将第i种物品分成了O(logn[i])种物品,将原问题转化为了复杂度为 O(V*∑logn[i])的01背包问题,是很大的改进。
混合三种背包问题:
时间限制: 1 Sec 内存限制: 128 MB
提交: 71 解决: 43
[提交][状态][讨论版][命题人: cbc]
一个旅行者有一个最多能用V公斤的背包,现在有n件物品,它们的重量分别是W1,W2,...,Wn,它们的价值分别为C1,C2,...,Cn。有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
第一行:二个整数,V(背包容量,V<=200),N(物品数量,N<=30);
第2..N+1行:每行三个整数Wi,Ci,Pi,前两个整数分别表示每个物品的重量,价值,第三个整数若为0,则说
明此物品可以购买无数件,若为其他数字,则为此物品可购买的最多件数(Pi)。
仅一行,一个数,表示最大总价值。
10 3
2 1 0
3 3 1
4 5 4
11
#include
#include
using namespace std;
int main()
{
int M,N,w[35],c[35],s[35],f[205]={0};
int i,j,k;
cin>>M>>N;
for(i=1;i<=N;i++)
scanf("%d %d %d",&w[i],&c[i],&s[i]);
for(i=1;i<=N;i++)
{
if(s[i]==0)//完全背包问题
{
for(j=w[i];j<=M;j++)
f[j]=max(f[j],f[j-w[i]]+c[i]);
}
else//0/1背包问题和多重背包问题
{
for(j=M;j>=0;j--)
{
for(k=1;k<=s[i];k++)
{
if(j-k*w[i]<0) break;
else
f[j]=max(f[j],f[j-k*w[i]]+k*c[i]);
}
}
}
}
printf("%d\n",f[M]);
return 0;
}
如果将01背包、完全背包、多重背包混合起来。也就是说,有的物品只可以取一 次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个 上限(多重背包)。应该怎么求解呢?
01背包与完全背包的混合
考虑到在01背包和完全背包中最后给出的伪代码只有一处不同,故如果只有两类物 品:一类物品只能取一次,另一类物品可以取无限次,那么只需在对每个物品应用转 移方程时,根据物品的类别选用顺序或逆序的循环即可,复杂度是O(VN)。