01背包
1:01背包
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8
思路:
题意为在n件物品中挑选体积不超过m的物品,使之总价值最大。我们令dp[i][j]表示有i件物品体积不超过j时的最大价值。那么对于每件物品 ,我们有选或者不选两种状态 。每次我们取最大价值的拿法,最终输出dp[n][m]即可。状态转移方程为dp[i][j]=max(dp[i-1][j],dp[i-1][j-v]+w) 。这里需要注意的是 ,动态规划中后面的每一项 ,都是由前一项转移而来 。我们可以对其空间进行优化 ,将二维数组改为一维数组 ,以dp[j]表示有i件物品体积不超过j时的最大价值 。此时在枚举体积时,我们需要从大到小枚举。因为我们在求i件物品时 ,需要的利用是i-1件物品时的数据。假如我们从小到大枚举 ,那么在求第i件物品 j体积时小于j的数值已经被我们更新为第i件时的数据了,导致我们无法进行后面的计算 。还需要注意的一个细节是 ,求体积恰好是m时最大价值和体积最大是m时的最大价值有什么区别呢? 区别就在于dp[j]数组的初始化不同。
1:当我们求的是体积恰好为m时的最大价值 ,我们需要把 dp[0] 初始化为 0 ,而dp[i] (1<=i<=m)初始化为负无穷 。为什么呢?因为此时dp[j]表示有0件物品且背包容量为j时的最大价值 。0件物品 ,只有当背包容量为0时 ,才能恰好装满背包,此时最大价值为0。负无穷表示其他条件下有 0件物品背包容量大于0时背包不可能装满 。
2:当我们求的是体积不超过j时的最大价值 ,那么我们将dp[j] (j>=0)都初始化为0 。表示在有 0件物品且不要求装满的情况下 ,无论如何最大价值都是 0 。
#include <stdio.h>
int n,m,a,b,dp[1005];
int max(int x,int y)
{
return x>y?x:y;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
{
scanf("%d%d",&a,&b);
for(int j=m;j>=a;j--)
{
dp[j]=max(dp[j],dp[j-a]+b);
}
}
printf("%d",dp[m]);
return 0;
}
2: 采药
辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。
为此,他想拜附近最有威望的医师为师。
医师为了判断他的资质,给他出了一个难题。
医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”
如果你是辰辰,你能完成这个任务吗?
输入格式
输入文件的第一行有两个整数T和M,用一个空格隔开,T代表总共能够用来采药的时间,M代表山洞里的草药的数目。
接下来的M行每行包括两个在1到100之间(包括1和100)的整数,分别表示采摘某株草药的时间和这株草药的价值。
输出格式
输出文件包括一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。
数据范围
1≤T≤1000,
1≤M≤100
输入样例:
70 3
71 100
69 1
1 2
输出样例:
3
#include <stdio.h>
int n,m,a,b,dp[1005];
int max(int x,int y)
{
return x>y?x:y;
}
int main()
{
scanf("%d%d",&m,&n);
for(int i=0;i<n;i++)
{
scanf("%d%d",&a,&b);
for(int j=m;j>=a;j--)
{
dp[j]=max(dp[j],dp[j-a]+b);
}
}
printf("%d",dp[m]);
return 0;
}
完全背包
1:完全背包问题
有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。
第 i 种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
10**
**思路:**完全背包和01背包的区别就是每件物品可以取无数次 ,类比01背包 ,很容易的我们便得到了状态转移方程 dp[i][j]=max(dp[i-1][j],dp[i-1][j -k * v ]+k * w)。 和01背包一样 ,这里我们也可以进行空间 上的优化 。下面的代码已经十分简洁了,仔细想想为什么可以这样写。。。。。。因为每件物品可以取无数次,所以可以从小向大写。
#include <stdio.h>
int dp[1001],v,w,n,m;
int max(int x,int y)
{
return x>y?x:y;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
{
scanf("%d%d",&v,&w);
for(int j=v;j<=m;j++)
{
dp[j]=max(dp[j],dp[j-v]+w);
}
}
printf("%d",dp[m]);
return 0;
}
多重背包
1 :多重背包问题
有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤100
0<vi,wi,si≤100
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10
**思路:**多重背包的特点就是每件物品有确定的数量 ,我们用dp[i][j]表示有i件物品体积最大是j时的最大价值 。状态转移方程为dp[i][j]=max(dp[i-1][j−c[i]∗k]+w[i]∗k,dp[i-1][j]); 同理 ,我们先可以对其进行空间上的优化·,以一维数组的形式储存数据。
第一份代码如下:
#include <stdio.h>
int dp[1001],n,m,v,w,s;
int max(int x,int y)
{
return x>y?x:y;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
{
scanf("%d%d%d",&v,&w,&s);
for(int j=m;j>=v;j--)
{
for(int k=1;k*v<=j&&k<=s;k++)
{
dp[j]=max(dp[j],dp[j-k*v]+k*w);
}
}
}
printf("%d",dp[m]);
return 0;
}
上面的代码可以解决一定数据范围的多重背包问题 。但是时间复杂度过高 。在这里我们还可以采用二进制优化 。即一个正整数 n,可以f分解为(1 ,2 ,4, 8·····最大数)的形式 。什么意思呢?举个例子 :n的值为10 ,那么10可以分解为 1 ,2 ,4,3 。为什么这样分解呢?因为这样一来 ,10以内的所有正整数均能用我们分解出来的数表示出来 。这样 ,我们便可以将多重背包转变为01背包,对不对,是不是很巧妙。
二进制优化的多重背包:
#include <stdio.h>
int dp[100000],n,m,v,w,s,t,k=0;
int max(int x,int y)
{
return x>y?x:y;
}
struct dalao
{
int V;
int W;
}p[100000];
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
{
scanf("%d%d%d",&v,&w,&s);
t=1;
while(t<=s)
{
s-=t;
p[k].V=t*v;
p[k].W=t*w;
t*=2;
k++;
}
if(s>0)
{
p[k].V=s*v;
p[k].W=s*w;
k++;
}
}
for(int i=0;i<k;i++)
{
for(int j=m;j>=p[i].V;j--)
{
dp[j]=max(dp[j],dp[j-p[i].V]+p[i].W);
}
}
printf("%d",dp[m]);
return 0;
}
上面的代码很好 ,但是多重背包还有更好的优化方法 。即用单调队列优化 。但是由于本文篇幅有限 ,有兴趣的可以自行百度 。hahahah``。。。
混合背包
1:混合背包问题
有 N 种物品和一个容量是 V 的背包。
物品一共有三类:
第一类物品只能用1次(01背包);
第二类物品可以用无限次(完全背包);
第三类物品最多只能用 si 次(多重背包);
每种体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
si=−1 表示第 i 种物品只能用1次;
si=0 表示第 i 种物品可以用无限次;
si>0 表示第 i 种物品可以使用 si 次;
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
−1≤si≤1000
输入样例
4 5
1 2 -1
2 4 1
3 4 0
4 5 2
输出样例:
8
**思路:**混合背包其实就是上面三种背包的混合 。将多重背包转化为01背包 ,分为01背包和完全背包两种情况求解。
#include <stdio.h>
int dp[100000],v,w,n,m,s,t,k=0;
int max(int x,int y)
{
return x>y?x:y;
}
struct dalao
{
int V;
int W;
int S;
}p[100000];
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
{
scanf("%d%d%d",&v,&w,&s);
if(s>0)
{
t=1;
while(t<=s)
{
s-=t;
p[k].V=v*t;
p[k].W=w*t;
p[k].S=-1;
k++;
t*=2;
}
if(s>0)
{
p[k].V=v*s;
p[k].W=w*s;
p[k].S=-1;
k++;
}
}
else
{
p[k].V=v;
p[k].W=w;
p[k].S=s;
k++;
}
}
for(int i=0;i<k;i++)
{
if(p[i].S==0)
{
for(int j=p[i].V;j<=m;j++)
{
dp[j]=max(dp[j],dp[j-p[i].V]+p[i].W);
}
}
else
{
for(int j=m;j>=p[i].V;j--)
{
dp[j]=max(dp[j],dp[j-p[i].V]+p[i].W);
}
}
}
printf("%d",dp[m]);
return 0;
}
分组背包
1:分组背包问题
有 N 组物品和一个容量是 V 的背包。
每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量。
接下来有 N 组数据:
每组数据第一行有一个整数 Si,表示第 i 个物品组的物品数量;
每组数据接下来有 Si 行,每行有两个整数 vij,wij,用空格隔开,分别表示第 i 个物品组的第 j 个物品的体积和价值;
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤100
0<Si≤100
0<vij,wij≤100
输入样例
3 5
2
1 2
2 4
1
3 4
1
4 5
输出样例:
8
思路: 类比于01背包,01背包有n件物品 ,每件物品有2个选择 ,即选或者不选 。我们可以把分组背包看成01背包 ,每组看成一个物品 ,对应的每组有s+1种选择 。第i组的每种选择都是建立在第i-1次的数据上。
#include <stdio.h>
int n,m,v[105],w[105],s,dp[105];
int max(int x,int y)
{
return x>y?x:y;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
{
scanf("%d",&s);
for(int i=0;i<s;i++)
{
scanf("%d%d",&v[i],&w[i]); //储存本组每种决策的代价和价值
}
for(int j=m;j>=0;j--) //对于本组的每一个决策 都是基于上一组的数据 所以从大到小
{
for(int k=0;k<s;k++) //循环本组的物品 同组对于本组同一重量间竞争 每个重量 对应于本组的一个或0个物品
{
if(v[k]<=j) //如果数据合法
{
dp[j]=max(dp[j],dp[j-v[k]]+w[k]);
}
}
}
}
printf("%d",dp[m]);
return 0;
}
**
二维费用的背包问题
**
有 N 件物品和一个容量是 V 的背包,背包能承受的最大重量是 M。
每件物品只能用一次。体积是 vi,重量是 mi,价值是 wi。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V,M,用空格隔开,分别表示物品件数、背包容积和背包可承受的最大重量。
接下来有 N 行,每行三个整数 vi,mi,wi,用空格隔开,分别表示第 i 件物品的体积、重量和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N≤1000
0<V,M≤100
0<vi,mi≤100
0<wi≤1000
输入样例
4 5 6
1 2 3
2 4 4
3 4 5
4 5 6
输出样例:
8
思路 : 其实就是在01背包的基础上 ,加了一层循环 ,写法和01背包一样。
#include <stdio.h>
int n,m,v,dp[1005][1005],a,b,c;
int max(int x,int y)
{
return x>y?x:y;
}
int main()
{
scanf("%d%d%d",&n,&v,&m); //件数 体积 重量
for(int i=0;i<n;i++)
{
scanf("%d%d%d",&a,&b,&c); //体积 重量 价值
for(int j=v;j>=a;j--)
{
for(int k=m;k>=b;k--)
{
dp[j][k]=max(dp[j][k],dp[j-a][k-b]+c);
}
}
}
printf("%d",dp[v][m]);
return 0;
}
背包问题求方案数
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出 最优选法的方案数。注意答案可能很大,请输出答案模 109+7 的结果。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示 方案数 模 109+7 的结果。
数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 601
输出样例:
2
思路: 因为我们要求方案数 ,为了方便记录 。我们用dp1[i]表示前i件物品体积恰好为j时的最大价值 。与01背包不同的是 ,我们需要另外开一个数组记录体积恰好为i时最大价值对应的方案数。对于每个物品 ,我们判断在体积恰好为j是否选择他,分为选他、不选他、选或者不选均是最大价值三种情况 。
#include <stdio.h>
int n,m,a,b,dp1[1005],dp2[1005],mod=1e9+7,s;
int max(int x,int y)
{
return x>y?x:y;
}
int main() //dp1[i]表示体积恰好是i时的最大价值 dp2[i]表示体积恰好为i时最大价值对应的方案数
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
dp1[i]=-1e9;
dp2[0]=1;
for(int i=0;i<n;i++)
{
scanf("%d%d",&a,&b); //体积 价值
for(int j=m;j>=a;j--)
{
s=0;
int t=max(dp1[j],dp1[j-a]+b);
if(t==dp1[j]) s+=dp2[j]; //判断那种方案价值最大 (有可能一样大)
if(t==dp1[j-a]+b) s+=dp2[j-a];
dp1[j]=t;
dp2[j]=s%mod;
}
}
int MAX=-1,ans=0;
for(int i=0;i<=m;i++)
{
MAX=max(dp1[i],MAX);
}
for(int i=0;i<=m;i++)
{
if(MAX==dp1[i])
ans+=dp2[i];
}
printf("%d",ans);
return 0;
}