一、01背包
有 n 件物品和一个容量为 v 的背包。第 i 件物品的费用为 w[i],价值是 c[i],在不超过背包最大容量的情况下是总价值最大。
【分析】
考虑到每种物品只有一件,所以这是一个 01背包问题,何为 01,就是对于一件物品,只有取1件或者取0件的选择。
用 f[i][j] 表示前 i 件物品消耗 j 体积获得的最大价值,显然,状态转移方程:f[i][j]=max{f[i-1][j],f[i-1][j-w[i]]+c[i]}。
【代码段】
for(int i=1;i<=n;i++)
for(int j=m;j>=1;j--)
{
if(j>=w[i])//如果剩余空间还能放第 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];
}
如果只用一维数组 f[i] 能不能达到一样的效果呢?把二维变成一维,用一个状态代表两个状态。
注意到上面的代码中,外层循环控制的是物品序号,所以每执行完一次就会得到前 i 件物品放入空间为 m 的最大价值。
如果转换成一维,用 f[v] 表示空间为 v 时获得的最大价值,同样是用外层循环控制物品序号,执行第 i 次前,
保存在数组中的是前 i-1 件物品放入空间为 m 的最大价值,不管它是怎么放的,我接下来都需要尝试能不能把第 i 件放进去,
把第 i 件放进去的理由只有:有足够空间放进去 and 相同空间消耗的情况下会比原来的价值高。
假设前 i-1 件物品已经把空间装满,这时是从满空间开始向前找还是全部拿出来从0空间开始放呢?显然是前者。
【代码段】
for(i=1;i<=n;i++)
for(j=m;j>=w[i];j--)
f[i]=max(f[i],f[i-w[i]]+c[i]);
二、完全背包
有 n 种物品和一个容量为 v 的背包。第 i 种物品的费用为 w[i],价值是 c[i],在不超过背包最大容量的情况下是总价值最大。
【分析】
和 01背包不同的是,这里的物品不限量,所以它的策略就不是简单的取0件或者取1件。
如果用二维数组,状态转移方程为:f[i][j]=max{f[i-1][j-kw[i]+kc[i]}(0<=k*w[i]<=j)
【代码段】
for(int i=1;i<=n;i++)
for(int 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];
}
同样,我们尝试把它转化成一维数组,
【代码段】
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(f[j-w[i]]+c[i]>f[j])
f[j]=f[j-w[i]]+c[i];
这里的 j 从小到大循环,为什么?因为在选了第 i 种物品后,还可以继续选,直到把空间用完。
所以在外层循环执行完第 i 次后,得到的 f[m] 就是前 i 种物品在限空间不限量的情况下选择得到的最大价值。
三、多重背包
有N种物品和一个容量为V的背包。第i种物品最多有a[i]件可用,每件费用是w[i],价值是c[i]。
求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
【分析】
类似于完全背包和01背包的结合,所以有朴素算法
【代码段】
for(i=1;i<=n;i++)
for(j=m;j>=0;j--)
for(k=0;k<=a[i];k++)
{
if(j<k*w[i])break;
f[j]=max(f[j-k*w[i]+k*c[i],f[j])
}
看到了吗!超越了 n2 的算法!爆零的气息!
首先明确一点,十进制数都可以转换成二进制数,也就是一堆2的次方相加(疯狂暗示 )
对,没错,我们把第 i 种物品分成若干物品,每件都有一个系数,新物品的价值和费用就是原来的价值和费用乘这个系数,
这里必须是不重复的拆分,因为拆分的目的就是把物品分解从而降低复制度,所以不会像二进制一样每个拆分数都是2的次方。
这些系数分别是 1、2、4、……2(k-1)、n-(2k+1)。前 i-1 个数的和是 2k+1,所以第 i 个数为 n-(2k+1)。
这里 k 是不大于 log2n[i] 的整数,n-(2k+1)是按2的次方拆分完成后的剩余部分。
【代码段】
n1=0;
for(i=1;i<=n;i++)//将每一件物品分成1、2、4、……2^(k-1)^、n-(2^k^+1)件
{
num=1;//从 1 开始
cin>>x>>y>>sum;//最多买 s 件,空间为 x,价值为 y
while(sum>=num)//还可以拆分
{
v[++n1]=x*num;//第 n1 件拆分物品的空间
w[n1]=y*num;//第 n1 件拆分物品的价值
sum-=num;
num<<=1;//扩大一倍
}
v[++n1]=x*sum;
w[n1]=y*sum;//余下的也要储存
}
for(i=1;i<=n1;i++)//按 01背包求解
for(j=m;j>=v[i];j--)
f[j]=max(f[j],f[j-v[i]]+w[i]);
这里我们将一个算法的复杂度由 n[i] 优化到 log2n[i],足以说明拆分的独特之处,需要特别注意。
四、混合背包
背包体积为V ,给出N个物品,每个物品占用体积为V[i],价值为W[i],
物品可以取一件,取 a[i] 件,或者取无数件,取无数件用0表示。
在所装物品总体积不超过V的前提下所装物品的价值的和的最大值是多少?
把三种基础背包整合起来,其实很简单。
#include<iostream>
using namespace std;
int n,m,w[101],c[101],a[101],f[101];
int max(int x,int y)
{
return x>y?x:y;
}
int main()
{
cin>>m>>n;
for(int i=1;i<=n;i++)
cin>>w[i]>>c[i]>>a[i];
for(int i=1;i<=n;i++)
{
if(!a[i])//完全背包
{
for(int j=w[i];j<=m;j++)
f[j]=max(f[j],f[j-w[i]]+c[i]);
}
else//01背包
{
int x=a[i];
for(int k=1;k<=x;k<<=1)//多重背包转换为01背包
{
for(int j=m;j>=w[i]*k;j--)
f[j]=max(f[j],f[j-w[i]*k]+c[i]*k);
x-=k;
}
if(x)
for(int j=m;j>=w[i]*x;j--)
f[j]=max(f[j],f[j-w[i]*x]+c[i]*x);
}
}
cout<<f[m]<<endl;
return 0;
}
五、二维背包
有N件物品和一个容量为V,载重为U的背包。第i件物品的体积是a[i],重量是b[i],价值是w[i]。
求解将哪些物品装入背包可使价值总和最大,输出最大的总价值。
【分析】
费用加了一维,只需状态也加一维即可。设 f[i][v][u] 表示前 i 件物品付出两种代价分别为 v 和 u 时可获得的最大价值。
状态转移方程就是:f[i][v][u]=max{f[i-1][v][u],f[i-1][v-a[i]][u-b[i]]+w[i]},把第一维的序号 i 去除,就变成了二维。
使用二维数组:当每件物品只可以取一次时变量v和u采用逆序的循环,当物品有如完全背包问题时采用顺序的循环。
当物品有如多重背包问题时拆分物品。有时,“二维费用”的条件是以这样一种隐含的方式给出的:最多只能取M件物品。
这事实上相当于每件物品多了一种“件数”的费用,每个物品的件数费用均为1,可以付出的最大件数费用为 M。
换句话说,设 f[v][m] 表示付出费用 v、最多选 m 件时可得到的最大价值,
则根据物品的类型(01、完全、多重)用不同的方法循环更新,最后在 f[0…V][0…M]范围内寻找答案。
另外,如果要求“恰取M件物品”,则在f[0…V][M]范围内寻找答案。
#include<iostream>
#include<cstring>
using namespace std;
int a[101],b[101],w[101];
int f[101][101];
int v,u,n,i,j,k;
int max(int x,int y)
{
return x>y?x:y;
}
int main()
{
cin>>n>>v>>u;
for(i=1;i<=n;i++)
cin>>a[i]>>b[i]>>w[i];
for(i=1;i<=n;i++)
for(j=v;j>=0;j--)
for(k=u;k>=0;k--)
{
int c1=j+a[i];
int c2=k+b[i];
c1=c1>v?v:c1;
c2=c2>u?u:c2;
f[c1][c2]=max(f[c1][c2],f[j][k]+w[i]);
}
cout<<f[v][u]<<endl;
return 0;
}
当发现由熟悉的动态规划题目变形得来的题目时,在原来的状态中加一维以满足新的限制是一种比较通用的方法。
六、分组背包
有N件物品和一个容量为V的背包,第i件物品的重量为c[i],价值为w[i],这些物品被划分成了若干组,
每组中的物品互相冲突,最多选一件,问将哪些物品放入背包中可以使背包获得最大的价值。
【分析】
我们用 f[k][v] 表示前 k 种物品花费费用 v 所能取得的最大价值。
状态转移方程:f[k][v]=max{f[k-1][v],f[k-1][v-c[i]]+w[i]|物品i属于第k组}
使用一维数组的伪代码:
for 所有的组k
for v=V……0
for 所有的 i 属于组 k
f[v]=max{f[v],f[v-w[i]]+c[i]}
这里的循环顺序,第二层必须在第三层外面,这样才能保证每组最多有一个物品被添加到背包。
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int N,V,T;
int v[101],w[101];
int f[101];
int a[11][101];
int main()
{
cin>>V>>N>>T; //容量,物品数,组数
for(int i=1;i<=N;i++)
{
int p;
cin>>v[i]>>w[i]>>p;
a[p][++a[p][0]]=i; //存每一组的所有物品的编号
//a[p][0]表示第p组一共有几个物品
}
for(int i=1;i<=T;i++)//从第一组开始
for(int j=V;j>=0;j--)//
for(int k=1;k<=a[i][0];k++)//枚举第一组的所有元素序号
if(j>=v[a[i][k]])
f[j]=max(f[j],f[j-v[a[i][k]]]+w[a[i][k]]);
cout<<f[V]<<endl;
return 0;
}
七、背包方案
给定 n 种面值,求组成面值为 m 的方案数。
设 f[m] 表示面值为 m 的方案数,根据动态规划的思想,有01背包写法和完全背包写法。
【01背包】
#include<cstdio>
using namespace std;
int a[101];
long long f[1001];//这里要开 long long
int main()
{
int n,m,i,j,k;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)
scanf("%d",&a[i]);
f[0]=1;
for(i=1;i<=n;i++)
for(j=m;j>=a[i];j--)
for(k=1;k<=j/a[i];k++)
f[j]+=f[j-k*a[i]];
printf("%lld",f[m]);
return 0;
}
【完全背包】
#include<cstdio>
using namespace std;
int a[101];
long long f[1001];
int main()
{
int n,m,i,j,k;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)
scanf("%d",&a[i]);
f[0]=1;
for(i=1;i<=n;i++)
for(j=a[i];j<=m;j++)
f[j]+=f[j-a[i]];
printf("%lld",f[m]);
return 0;
}
来源:https://blog.csdn.net/Alex_Colon/article/details/98451605