2. 01背包问题
题意:有n个物品,背包总容量为v,第 i 个物品的体积为
v
i
v_i
vi,价值为
w
i
w_i
wi,求装入背包的最大价值
思路:n个物品,每个物品只能选一次。设
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示前i个物品,体积不超过 j 的最大价值
分析:从右往左枚举体积,可以保证每一个
f
[
i
]
[
j
]
f[i][j]
f[i][j]是用
f
[
i
−
1
]
[
j
−
c
]
f[i-1][j-c]
f[i−1][j−c]转移过来的
#include <iostream>
using namespace std;
int n,m,c,v;
int f[1010];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;++i)
{
cin>>c>>v;
for(int j=m;j>=c;--j)
f[j]=max(f[j],f[j-c]+v);
}
cout<<f[m]<<endl;
return 0;
}
3. 完全背包问题
题意:有n类物品,背包总容量为v,第i类物品的体积为
v
i
v_i
vi,价值为
w
i
w_i
wi,求装入背包的最大价值
思路:状态方程同上,f[i][j]表示前i类物品的体积不超过j的最大价值
分析:在01背包中,我们为了确保状态f[i][j]是从f[i-1][j-c]转移过来的,我们选择了从后往前遍历。而完全背包恰好相反,因为我们可以选择的物品是无限的。我们的状态
f
[
i
]
[
j
]
f[i][j]
f[i][j]应该是已经做过决策的
f
[
i
]
[
j
−
c
]
f[i][j-c]
f[i][j−c]转移过来的。所以应该从前往后遍历
#include <iostream>
using namespace std;
int n,m,c,v;
int f[1010];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;++i)
{
cin>>c>>v;
for(int j=c;j<=m;++j)
f[j]=max(f[j],f[j-c]+v);
}
cout<<f[m]<<endl;
return 0;
}
4. 多重背包问题 I
题意:有n类物品,背包总容量为v,第i类物品,有
s
i
s_i
si件体积为
v
i
v_i
vi,价值为
w
i
w_i
wi,求装入背包的最大价值
思路:状态设置还是
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示前i个物品总体积不超过 j 的最大价值
分析:其实也和01背包、完全背包没什么区别。只不过决策的次数变成了s次,物品、总体积、物品件数都在100以内。暴力枚举决策就好了
#include <iostream>
using namespace std;
int n,m,c,v,s;
int f[110];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;++i)
{
cin>>c>>v>>s;
for(int j=m;j>=c;--j)
for(int k=1;k<=s&&k*c<=j;++k)
f[j]=max(f[j],f[j-k*c]+k*v);
}
cout<<f[m]<<endl;
return 0;
}
5. 多重背包问题 II
题意:有n类物品,背包总容量为v,第i类物品,有
s
i
s_i
si件体积为
v
i
v_i
vi,价值为
w
i
w_i
wi,求装入背包的最大价值
思路:状态设置还是
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示前i个物品总体积不超过 j 的最大价值
分析:这里和上题的区别在于,n,m,s的范围在2000以内。继续暴力决策的话,明显会超时。因此可以考虑二进制优化,按二进制的方式,把一类物品拆分成多个物品
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
int n,m,c,v,s;
int f[2010];
struct Thing
{
int c,v;
};
vector<Thing> things;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;++i)
{
cin>>c>>v>>s;
for(int k=1;k<=s;k*=2)
{
s-=k;
things.push_back({c*k,v*k});
}
if(s>0)
things.push_back({c*s,v*s});
}
for(auto it: things)
for(int j=m;j>=it.c;--j)
f[j]=max(f[j],f[j-it.c]+it.v);
cout<<f[m]<<endl;
return 0;
}
6. 多重背包问题 III
题意:有n类物品,背包总容量为v,第i类物品,有
s
i
s_i
si件体积为
v
i
v_i
vi,价值为
w
i
w_i
wi,求装入背包的最大价值
思路:状态设置还是
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示前i个物品总体积不超过 j 的最大价值
分析:这里n范围在1000,v的范围在20000,s的范围在20000以内。如果还是采用二进制优化的话。时间复杂度是:
1000
×
l
o
g
2
(
20000
)
×
20000
1000\times log_2(20000)\times 20000
1000×log2(20000)×20000,大约是3e8,会超时。所以可以采用单调队列优化的方法
优化思路:我们发现每一个
f
[
i
]
[
j
]
f[i][j]
f[i][j]其实都是从
f
[
i
−
1
]
[
j
−
c
]
、
f
[
i
−
1
]
[
j
−
2
∗
c
]
、
…
、
f
[
i
−
1
]
[
j
−
s
∗
c
]
f[i-1][j-c]、f[i-1][j-2*c]、\dots、f[i-1][j-s*c]
f[i−1][j−c]、f[i−1][j−2∗c]、…、f[i−1][j−s∗c]转移过来的,因此我们可以根据余数 r 分组,每一组是:
r
、
r
+
c
、
r
+
2
c
、
…
r、r+c、r+2c、\dots
r、r+c、r+2c、…,维护一个最大值单调队列,每次先判断入队,然后缩减区间,最后更新
f
[
j
]
f[j]
f[j]
#include <iostream>
#include <cstring>
using namespace std;
const int maxn=20010;
int n,m,c,v,s;
int Q[maxn],f[maxn],g[maxn];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;++i)
{
memcpy(g,f,sizeof(f));
cin>>c>>v>>s;
for(int r=0;r<c;++r)
{
int head=1,tail=0;
for(int j=r;j<=m;j+=c)
{
while(head<=tail&&g[Q[tail]]-(Q[tail]-r)/c*v<=g[j]-(j-r)/c*v)
tail--;
Q[++tail]=j;
while(head<=tail&&j-Q[head]>s*c)
head++;
f[j]=max(f[j],g[Q[head]]+(j-Q[head])/c*v);
}
}
}
cout<<f[m]<<endl;
return 0;
}
7. 混合背包问题
题意:n中物品,背包容量为m,有三类物品,第一类只能用一次,第二类能用无限次,第三类能用
s
i
s_i
si次,物品的体积为
v
i
v_i
vi,价值是
w
i
w_i
wi,求背包能容纳的最大价值
思路:三种背包的混合,01背包和多重背包,可以分为一类处理
#include <iostream>
#include <vector>
using namespace std;
int n,m,c,v,s;
int f[1010];
struct Thing
{
int f,c,v;
};
vector<Thing> things;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;++i)
{
cin>>c>>v>>s;
if(s<0)
things.push_back({-1,c,v});
else if(s==0)
things.push_back({0,c,v});
else if(s>0)
{
for(int k=1;k<=s;k*=2)
{
s-=k;
things.push_back({-1,k*c,k*v});
}
if(s>0)
things.push_back({-1,s*c,s*v});
}
}
for(auto it: things)
{
if(it.f==-1)
{
for(int j=m;j>=it.c;--j)
f[j]=max(f[j],f[j-it.c]+it.v);
}
else
{
for(int j=it.c;j<=m;++j)
f[j]=max(f[j],f[j-it.c]+it.v);
}
}
cout<<f[m]<<endl;
return 0;
}
8. 二维费用的背包问题
题意:有n件物品,容量为v,背包能承受的最大重量是m。每个物品只能用一次,体积是
v
i
v_i
vi,重量是
m
i
m_i
mi,价值是
w
i
w_i
wi,求在不超过容量,不超过体积情况下,能够装入背包的最大价值。
思路:三维dp,
f
[
i
]
[
j
]
[
k
]
f[i][j][k]
f[i][j][k]表示前i中物品体积不超过j,重量不超过k,能获取的最大价值。同样的第一维可以省略。
#include <iostream>
using namespace std;
int n,m1,m2,c1,c2,v;
int f[110][110];
int main()
{
cin>>n>>m1>>m2;
for(int i=1;i<=n;++i)
{
cin>>c1>>c2>>v;
for(int j=m1;j>=c1;--j)
for(int k=m2;k>=c2;--k)
f[j][k]=max(f[j][k],f[j-c1][k-c2]+v);
}
cout<<f[m1][m2]<<endl;
return 0;
}
9. 分组背包问题
题意:有n组物品,背包容量为v,每组的物品有若干个,但每组的物品只能选一个,求背包的最大价值
分析:其实多重背包是分组背包的一个特殊情况,分组背包中的每一个物品都是不一样的。所以只能暴力枚举每一个决策。而多重背包中因为物品相同,就可以做二进制优化、单调队列优化
#include <iostream>
using namespace std;
int n,m,s,c[110],v[100];
int f[110];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;++i)
{
cin>>s;
for(int j=1;j<=s;++j)
cin>>c[j]>>v[j];
for(int j=m;j>=0;--j)
for(int k=1;k<=s;++k)
if(j>=c[k])
f[j]=max(f[j],f[j-c[k]]+v[k]);
}
cout<<f[m]<<endl;
return 0;
}
10. 有依赖的背包问题
11. 背包问题求方案数
题意:在01背包的基础上,问取到最优的方案数有多少
思路:在f[j]在基础上需要再开一个g[j]数组记录路径数。然后我们所有的答案都是从f[0]、g[0]开始转移的
#include <iostream>
#include <cstring>
using namespace std;
const int mod=1e9+7;
int n,m,c,v;
//g[i]表示体积恰好是i的情况下的方案数
//f[i]表示体积恰好是i的情况下的最大价值
int f[1010],g[1010];
int main()
{
memset(f,-0x3f,sizeof(f));
f[0]=0,g[0]=1;
cin>>n>>m;
for(int i=1;i<=n;++i)
{
cin>>c>>v;
for(int j=m;j>=c;--j)
{
if(f[j]<f[j-c]+v)
g[j]=g[j-c];
else if(f[j]==f[j-c]+v)
g[j]=(g[j]+g[j-c])%mod;
f[j]=max(f[j],f[j-c]+v);
}
}
int maxx=0,ans=0;
for(int i=0;i<=m;++i)
maxx=max(maxx,f[i]);
for(int i=0;i<=m;++i)
if(f[i]==maxx)
ans=(ans+g[i])%mod;
cout<<ans<<endl;
return 0;
}
12. 背包问题求具体方案
题意:在01背包的基础上求一个具体方案,要求字典序最小
思路:我们观察f[i][j]这个解,如果说f[i][j]=f[i-1][j]说明,我们不选,第i个物品可以得到最优解。如果说f[i][j]=f[i-1][j-c]+v,说明我们选择第i个物品得到最优解。当然可能,两种情况都相等,那就说明有两种最优解方案,取字典序最小的即可。
因为我们只能从得到的最优解,往前面推方案。所以我们在dp的时候要从第n个物品往第1个物品推,这样我们才能从第1个物品,即最优解出发,贪心地选择物品,对于前面的物品,能选则选
#include <iostream>
#include <cstring>
using namespace std;
const int mod=1e9+7;
int n,m,c[1010],v[1010];
int f[1010][1010];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;++i)
cin>>c[i]>>v[i];
for(int i=n;i>=1;--i)
{
for(int j=0;j<=m;++j)
{
f[i][j]=f[i+1][j];
if(j-c[i]>=0)
f[i][j]=max(f[i+1][j],f[i+1][j-c[i]]+v[i]);
}
}
int ans=f[1][m],vol=m;
for(int i=1;i<=n;++i)
{
if(vol-c[i]>=0&&f[i+1][vol-c[i]]+v[i]==ans)
{
ans-=v[i];
vol-=c[i];
cout<<i<<" ";
}
}
return 0;
}