一.0/1背包
题目描述
一个旅行者有一个最多能用 m 公斤的背包,现在有 n 件物品,它们的重量分别是 W1 ,W2 ,... , Wn ,它们的价值分别为 C1,C2 ,... ,Cn 。若每种物品只有一件求旅行者能获得最大总价值。
输入格式
第 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,表示每个物品的重量和价值。
输出格式
仅一行,一个数,表示最大总价值。
样例数据 1
输入
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
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<
单调队列优化:
#include
using namespace std;
const int Max=2005;
int n,m,ans,t,head,tail;
int w[Max],c[Max],v[Max],f[Max],p[Max];
inline int calc(int i,int u,int k){return f[u + k*v[i]] - k*w[i];}
int main()
{
scanf("%d",&t);
while(t--)
{
memset(f,0xcf,sizeof(f));
memset(p,0,sizeof(p));
f[0]=0;
scanf("%d%d",&m,&n);
for(int i=1;i<=n;i++)
{
scanf("%d%d%d",&v[i],&w[i],&c[i]);
for(int u=0;u=max(0,maxp-c[i]);k--)
{
while(head <= tail && calc(i,u,k) >= calc(i,u,p[tail])) tail--;
p[++tail] = k;
}
for(int q=maxp;q>=0;q--)
{
while(head <= tail && p[head] > q - 1) head++;
if(head <= tail) f[u + q*v[i]] = max(calc(i,u,p[head]) + q*w[i],f[u + q*v[i]]);
if(q-c[i]-1 >= 0)
{
while(head <= tail && calc(i,u,p[tail]) <= calc(i,u,q-c[i]-1)) tail--;
p[++tail] = q-c[i]-1;
}
}
}
}
ans=0;
for(int i=1;i<=m;i++) ans = max(ans,f[i]);
cout<
四.分组背包
题目描述
设有 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][k]) f[j]=max(f[j],f[j-w[i][k]]+c[i][k]);