一.01背包
题目:有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
解析:每种物品仅有一件,可以选择放或不放。
f[i][v]表示前i件物品放入容量为v的背包可以获得的最大价值。则其状态转移方程便是:f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}。
空间优化后,要求在每次主循环中我们以v=V…0的顺序推f[v],这样才能保证推f[v]时f[v-c[i]]保存的是状态f[i-1][v-c[i]]的值。
如果将v的循环顺序从上面的逆序改成顺序的话,那么则成了f[i][v]由f[i][v-c[i]]推知,与本题意不符。
#include<bits/stdc++.h>
using namespace std;
int main()
{
int t,m;
scanf("%d%d",&t,&m);
int t1[1005],val[1005],f[1005];
memset(f,0,sizeof(f));
for(int i=1;i<=m;++i)scanf("%d%d",&t1[i],&val[i]);
for(int i=1;i<=m;++i)
{
for(int j=t;j>=t1[i];--j)
{
f[j]=max(f[j],f[j-t1[i]]+val[i]);
}
}
printf("%d",f[t]);
return 0;
}
二.完全背包
题目:有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
解析:令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值。f[i][v]=max{f[i-1][v-kc[i]]+kw[i]|0<=k*c[i]<=v}。
转化为01背包求解:01背包要保证第i次循环中的状态f[i][v]是由状态f[i-1][v-c[i]]递推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的子结果f[i-1][v-c[i]]。而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第i种物品”这种策略时,却正需要一个可能已选入第i种物品的子结果f[i][v-c[i]],所以就可以并且必须采用v=0…V的顺序循环。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1005;
int n,wei[maxn],val[maxn],m;
int dp[maxn];
int main()
{
scanf("%d%d",&m,&n);
for(int i=1;i<=n;++i)
{
scanf("%d%d",&wei[i],&val[i]);
}
for(int i=1;i<=n;++i)
{
for(int j=wei[i];j<=m;++j)
{
dp[j]=max(dp[j],dp[j-wei[i]]+val[i]);
}
}
printf("%d",dp[m]);
return 0;
}
三.多重背包
题目:有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
因为对于第i种物品有n[i]+1种策略:取0件,取1件……取n[i]件。令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值,则:f[i][v]=max{f[i-1][v-kc[i]]+kw[i]|0<=k<=n[i]}。复杂度是O(V*Σn[i])。
法一:暴力!
#include<bits/stdc++.h>
using namespace std;
const int maxn1=505,maxn2=6005;
int n,wei[maxn2],val[maxn2],m,num[maxn2];
int dp[maxn1][maxn2];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
scanf("%d%d%d",&wei[i],&val[i],&num[i]);
for(int i=1;i<=n;++i)
{
for(int j=0;j<=m;++j)
{
for(int k=0;k<=num[i];++k)
{
if(j-k*wei[i]>=0)
{
dp[i][j]=max(dp[i][j],dp[i-1][j-k*wei[i]]+k*val[i]);
}
}
}
}
printf("%d",dp[n][m]);
}
法二:二进制优化
1~ m可以用这些数的组合构成: 1 2 4 8…2^x (+ 剩余的数)。1到n以内的数字,能够通过 n 内的进制数组合得到,所以,我们可以利用二进制数的拆分求出所有 n 以内的数;拆分的过程:9 - 1 = 8; 8 - 2 = 6; 6 - 4 = 2 ; 2 - 8 < 0; 那么要求的 9 以内的二进制数就是 1,2, 4,2,这几个二进制是能够组成 9以内包括 9 的所有整数,然后通过 w[ i ] 乘以这些二进制数,把他们当作一件物品来做01背包的处理。 因为这些数字可以组成(1~最大数)中的任何一个数我们可以实现组合把这些抽出来变成一个新的产品,不过 重量和价值 有所不同。
#include<bits/stdc++.h>
using namespace std;
const int maxn1=505,maxn2=6005;
int n,wei[maxn2],val[maxn2],m,num[maxn2];
int dp[maxn2];
int tot;
int new_wei[maxn2],new_val[maxn2];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
{
scanf("%d%d%d",&wei[i],&val[i],&num[i]);
}
for(int i=1;i<=n;++i)
{
for(int j=1;j<=num[i];j<<=1)
{
num[i]-=j;
new_wei[++tot]=wei[i]*j;
new_val[tot]=val[i]*j;
}
if(num[i])
{
new_wei[++tot]=wei[i]*num[i];
new_val[tot]=val[i]*num[i];
num[i]=0;
}
}
for(int i=1;i<=tot;++i)
{//转化为零壹背包问题
for(int j=m;j>=new_wei[i];--j)
{
dp[j]=max(dp[j],dp[j-new_wei[i]]+new_val[i]);
}
}
printf("%d",dp[m]);
return 0;
}
四.混合背包
题目:如果将P01、P02、P03混合起来。也就是说,有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。应该怎么求解呢?
#include<bits/stdc++.h>
using namespace std;
const int maxn=2005;
int n,m;
int w[maxn],v[maxn],nu[maxn];
int dp[maxn];
int main()
{
scanf("%d%d",&m,&n);
for(int i=1;i<=n;++i)
{
scanf("%d%d%d",&w[i],&v[i],&nu[i]);
}
for(int i=1;i<=n;++i)
{
if(nu[i]==0)
{//完全背包
for(int j=w[i];j<=m;++j)
{
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
}
else
{//01背包和多重背包
for(int j=0;j<=nu[i];++j)
{//j次01背包
for(int k=m;k>=w[i];--k)
{
dp[k]=max(dp[k],dp[k-w[i]]+v[i]);
}
}
}
}
printf("%d",dp[m]);
return 0;
}
五.费用二维背包
例一: 潜水员
题目传送门
【题目描述】
潜水员为了潜水要使用特殊的装备。他有一个带2种气体的气缸:一个为氧气,一个为氮气。让潜水员下潜的深度需要各种的数量的氧和氮。潜水员有一定数量的气缸。每个气缸都有重量和气体容量。潜水员为了完成他的工作需要特定数量的氧和氮。他完成工作所需气缸的总重的最低限度的是多少?
例如:潜水员有5个气缸。每行三个数字为:氧,氮的(升)量和气缸的重量:
3 36 120
10 25 129
5 50 250
1 45 130
4 20 119
如果潜水员需要5升的氧和60升的氮则总重最小为249(1,2或者4,5号气缸)。
你的任务就是计算潜水员为了完成他的工作需要的气缸的重量的最低值。
【输入】
第一行有2整数m,n(1≤m≤21,1≤n≤79)。它们表示氧,氮各自需要的量。
第二行为整数k(1≤n≤1000)表示气缸的个数。
此后的k行,每行包括ai,bi,ci(1≤ai≤21,1≤bi≤79,1≤ci≤800)3
整数。这些各自是:第i个气缸里的氧和氮的容量及汽缸重量。
【输出】
仅一行包含一个整数,为潜水员完成工作所需的气缸的重量总和的最低值。
【输入样例】
5 60
5
3 36 120
10 25 129
5 50 250
1 45 130
4 20 119
【输出样例】249
思路:正常的此背包问题的转移方程是dp[j][t]=min(dp[j-oo[i]][t-nn[i]]+wei[i],dp[j][t]),然而我们要考虑一个问题:普通背包问题的合法状态是不可以超出背包容量的,所以其最优解为dp[m][n],但此题的装入的气体体积可以超过限制的容量,导致dp[m][n]
此时并不是最优解。
我们将转移方程变形可得:dp[j+oo[i]][t+nn[i]]=min(dp[j+oo[i]][t+nn[i]],dp[j][t]+wei[i]);
若j+oo[i]>m,t+nn[i]>n,即气体已经满足容量,那么dp[j+oo[i]][t+nn[i]]和dp[m][n]等效
#include<bits/stdc++.h>
using namespace std;
const int maxn=5005;
int n,m,dp[505][505],k;
int oo[maxn],nn[maxn],wei[maxn];
int main()
{
scanf("%d%d%d",&m,&n,&k);
memset(dp,63,sizeof(dp));
dp[0][0]=0;//唯一初始合法状态
for(int i=1;i<=k;++i)
{
scanf("%d%d%d",&oo[i],&nn[i],&wei[i]);
}
for(int i=1;i<=k;++i)
{
for(int j=m;j>=0;--j)
{
for(int t=n;t>=0;--t)
{
int temp1=j+oo[i],temp2=t+nn[i];
if(temp1>m)temp1=m;
if(temp2>n)temp2=n;
dp[temp1][temp2]=min(dp[temp1][temp2],dp[j][t]+wei[i]);
//即dp[j+oo[i]][t+nn[i]]=min(dp[j+oo[i]][t+nn[i]],dp[j][t]+wei[i]);
//而j+oo[i]可以超出m,t+oo[i]可以超出n,故要用m,n替换
}
}
}
printf("%d",dp[m][n]);
return 0;
}
例二:通天潜水
题目传送门
题目描述
在猴王的帮助下,小A终于走出了这篇荒山,却发现一条波涛汹涌的河拦在了自己的面前。河面上并没有船,但好在小A有n个潜水工具。由于他还要背重重的背包,所以他只能背m重的工具,又因为他的力气并不是无限的,河却很宽,所以他只能背有v阻力的工具。但是这条河下有非常重要的数据,所以他希望能够停留的时间最久。于是他找到了你,让你告诉他方案。
输入格式
三个数m,v,n如题目所说
接下来n行,每行三个数ai,bi,ci分别表示所含的重力,阻力,能够支撑的时间
输出格式
第一行一个数,表示最长的时间
接下来一行,若干个数,表示所选的物品
输入 #1
100 100 3
50 60 289
40 10 116
50 50 106
输出 #1
405
1 2
说明/提示
1<=m,v<=200,n<=100
AC code:
#include<bits/stdc++.h>
using namespace std;
const int maxn=2005;
int m,n,v;
int g[maxn],f[maxn],ti[maxn];
int dp[205][205];
int road[205][205][105];//road[i][j][]用来记录到dp[i][j]的所走过的编号
int main()
{
scanf("%d%d%d",&m,&v,&n);
for(int i=1;i<=n;++i)
{
scanf("%d%d%d",&g[i],&f[i],&ti[i]);
}
for(int i=1;i<=n;++i)
{
for(int j=m;j>=g[i];--j)
{
for(int t=v;t>=f[i];--t)
{
int temp1=j-g[i],temp2=t-f[i];
if(dp[temp1][temp2]+ti[i]>dp[j][t])
{
dp[j][t]=dp[temp1][temp2]+ti[i];
for(int node=1;node<=road[temp1][temp2][0];++node)
{
//road[i][j][0]表示到dp[i][j]编号的个数
road[j][t][node]=road[temp1][temp2][node];
}
road[j][t][0]=road[temp1][temp2][0]+1;
road[j][t][road[j][t][0]]=i;
}
}
}
}
printf("%d\n",dp[m][v]);
for(int i=1;i<=road[m][v][0];++i)
{
printf("%d ",road[m][v][i]);
}
return 0;
}
六.分组背包
题目:
有N件物品,告诉你这N件物品的重量以及价值,将这些物品划分为K组,每组中的物品互相冲突,最多选一件,求解将哪些物品装入背包可使这些物品的费用综合不超过背包的容量,且价值总和最大。
解法:
这个问题变成了每组物品有若干种策略:是选择本组的某一件,还是一件都不选。也就是说设f[k][v]表示前k组物品花费费用v能取得的最大权值,则有f[k][v]=max{f[k-1][v],f[k-1][v-c[i]]+w[i]|物品i属于第k组}。
#include<bits/stdc++.h>
using namespace std;
const int maxn=2005;
int v,n,k;
int tot[maxn];
struct node
{
int wei,val;
}code[maxn][maxn];
int dp[maxn];
int main()
{
scanf("%d%d%d",&v,&n,&k);
for(int i=1;i<=n;++i)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
tot[c]++;
code[c][tot[c]]=(node){a,b};
}
for(int i=1;i<=k;++i)
{
for(int j=v;j>=0;--j)
{
for(int temp=1;temp<=tot[i];++temp)
{
if(j>=code[i][temp].wei)
{
dp[j]=max(dp[j],dp[j-code[i][temp].wei]+code[i][temp].val);
}
}
}
}
printf("%d",dp[v]);
return 0;
}