背包问题
一.0/1背包
输入格式
第 1 行:两个整数,M(背包容量,M≤200)和 N(物品数量,N≤30)。
第 2..N+1 行:每行二个整数 Wi,Ci,表示每个物品的重量和价值。
输出格式
仅一行,一个数,表示最大总价值。
样例数据 1
输入
10 4
2 1
3 3
4 5
7 9
输出
12
解析:
令F[i][j]表示从前 i 个物品中选出了总体积为 j 的物品放入背包,物品的最大价值和。
于是就有F[i][j] = max(F[i - 1][ j] , F[i - 1][j - Vi] + Wi)。其中F[i - 1][ j]表示不选第 i 个物品,F[i - 1][j - Vi] + Wi表示选第 i
个物品,于是0/1背包解法也就很明确了。
memset(f,0xcf,sizeof(f)); //-INF
f[0][0] = 0;
for(int i = 1;i <= n;i++)
{
for(int j = 0;j <= m;j++) f[i][j]=f[i-1][j];
for(int j = v[i];j <=m;j++) f[i][j] = max(f[i - 1][j - v[i]] + w[i],f[i][j]);;
}
通过状态转移方程,我们发现,每一阶段 i 的状态至于 i - 1 的状态有关,于是可以用滚动数组降维。
int f[2][Max];
memset(f,0xcf,sizeof(f)); //-INF
f[0][0] = 0;
for(int i = 1;i <= n;i++)
{
for(int j = 0;j <= m;j++) f[i & 1][j]=f[(i-1) & 1][j];
for(int j = v[i];j <=m;j++) f[i & 1][j] = max(f[(i - 1) & 1][j - v[i]] + w[i],f[i & 1][j]);;
}
接着我们发现,每个阶段开始执行时,执行了从 i - 1 到 i 的拷贝操作,于是我们可以直接省略掉第一维。
int f[Max];
memset(f,0xcf,sizeof(f));
f[0]=0;
for(int i=1;i<=n;i++)
for(int j=m;j>=w[i];j--)
f[j]=max(f[j],f[j-w[i]]+c[i]);
注意第二层循环的 j 是倒序循环的,这是为了保证每个物品是唯一且只能放入背包一次。
二.完全背包
设有 n 种物品,每种物品有一个重量及一个价值。但每种物品的数量是无限的,同时有一个背包,最大载重量为 M ,今从 n 种物品中选取若干件(同一种物品可以多次选取),使其重量的和小于等于 M ,而价值的和为最大。
第 1 行:两个整数,M(背包容量,M<=200)和 N(物品数量,N<=200)。
第 2..N+1 行:每行二个整数 Wi,Ci,表示每个物品的重量和价值。
仅一行,一个数,表示最大总价值。
输入
12 4
2 1
3 3
4 5
7 9
15
解析:
与0/1 背包同样的思想,只需注意第二层循环的 j 是正序循环的,这就对应每种物品可以使用无限次。
int f[Max];
memset(f,0xcf,sizeof(f));
f[0]=0;
for(int i=1;i<=n;i++)
for(int j=w[i];j<=m;j++)
f[j]=max(f[j],f[j-w[i]]+c[i]);
三.多重背包
设有 n 种物品,每种物品有一个重量及一个价值。每种物品的数量为c[i],同时有一个背包,最大载重量为 M ,今从 n 种物品中选取若干件(同一种物品可以多次选取),使其重量的和小于等于 M ,而价值的和为最大。
解析:
三种方法:直接拆分,二进制拆分与单调队列优化。有兴趣的读者可自行查阅相关资料。
二进制拆分(HUD2191)
#include <bits/stdc++.h>
using namespace std;
const int Max=1011;
int n,m,ans,t,tot;
int w[105],c[105],num[105],val[Max],size[Max];
int f[Max];
inline int get_int()
{
int x=0,f=1;
char c;
for(c=getchar();(!isdigit(c))&&(c!='-');c=getchar());
if(c=='-') {f=-1;c=getchar();}
for(;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+c-'0';
return x*f;
}
int main()
{
t=get_int();
while(t--)
{
tot=ans=0;
memset(f,-1,sizeof(f));
n=get_int();
m=get_int();
for(int i=1;i<=m;i++) w[i]=get_int(),c[i]=get_int(),num[i]=get_int();
for(register int i=1;i<=m;i++)
{
for(register int j=1;j<=num[i];j<<=1)
{
size[++tot]=j*w[i];
val[tot]=j*c[i];
num[i]-=j;
}
if(num[i] > 0)
{
size[++tot]=num[i]*w[i];
val[tot]=num[i]*c[i];
}
}
f[0]=0;
for(register int i=1;i<=tot;i++)
for(register int j=n;j>=size[i];j--)
f[j]=max(f[j],f[j-size[i]]+val[i]);
for(int i=0;i<=n;i++) ans=max(ans,f[i]);
cout<<ans<<"\n";
}
return 0;
}
四.分组背包
设有 n 组物品,每组有ci个物品,第i组的第j个物品的中重量为Vi,j,价值为Wi,j,同时有一个背包,最大载重量为 M ,今从 物品中选取若干件(同一种物品可以多次选取),使得每组至多选一个物品并且重量的和小于等于 M ,价值的和为最大。
解析:
思路相似,直接看代码吧
int f[Max];
memset(f,0xcf,sizeof(f));
f[0]=0;
for(int i=1;i<=n;i++)
for(int j=m;j>=0;j--)
for(int k=1;k<=c[i];k++)
if(j >= w[i][j]) f[j]=max(f[j],f[j-w[i][k]]+c[i][k]);