基本思路
- 第一维枚举物品、第二维枚举体积、第三维枚举决策
01背包
题意:有n个物品,背包总容量为v,第 i 个物品的体积为
v
i
v_i
vi,价值为
w
i
w_i
wi,求装入背包的最大价值
思路:
{
状
态
表
示
f
[
i
]
[
j
]
=
{
集
合
:
所
有
只
考
虑
前
i
种
物
品
且
总
体
积
不
超
过
j
的
方
案
的
集
合
属
性
:
取
其
中
价
值
最
大
的
方
案
状
态
计
算
:
找
最
后
一
个
不
同
点
,
对
第
i
个
物
品
取
或
者
不
取
\begin{cases} 状态表示f[i][j]= {\begin{cases} 集合:所有只考虑前i种物品且总体积不超过j的方案的集合\\ 属性:取其中价值最大的方案 \end{cases}}\\ 状态计算:找最后一个不同点,对第 i 个物品取或者不取 \end{cases}
⎩⎪⎨⎪⎧状态表示f[i][j]={集合:所有只考虑前i种物品且总体积不超过j的方案的集合属性:取其中价值最大的方案状态计算:找最后一个不同点,对第i个物品取或者不取
f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − w [ i ] ] + v [ i ] ) f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i]) f[i][j]=max(f[i−1][j],f[i−1][j−w[i]]+v[i])
这里第一维可以省略,只需要将dp方程等价转换。
在更新
f
[
i
]
[
j
]
f[i][j]
f[i][j] 的时候需要用到的
f
[
i
−
1
]
[
j
−
w
[
i
]
]
f[i-1][j-w[i]]
f[i−1][j−w[i]],所以从右往左更新
未优化
#include <iostream>
using namespace std;
const int maxn=1010;
int n,m,c[maxn],v[maxn],dp[maxn][maxn];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
scanf("%d%d",&c[i],&v[i]);
for(int i=1;i<=n;++i)
for(int j=m;j>=0;--j)
{
dp[i][j]=dp[i-1][j];
if(j>=c[i])
dp[i][j]=max(dp[i-1][j],dp[i-1][j-c[i]]+v[i]);
}
printf("%d\n",dp[n][m]);
return 0;
}
空间优化
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=1010;
int n,m,w[maxn],v[maxn],f[maxn];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
scanf("%d%d",&w[i],&v[i]);
for(int i=1;i<=n;++i)
for(int j=m;j>=w[i];--j)
f[j]=max(f[j],f[j-w[i]]+v[i]);
printf("%d\n",f[m]);
return 0;
}
完全背包问题
思路:
{
状
态
表
示
f
[
i
]
[
j
]
=
{
集
合
:
所
有
只
考
虑
前
i
种
物
品
且
总
体
积
不
超
过
j
的
方
案
的
集
合
属
性
:
取
其
中
价
值
最
大
的
方
案
状
态
计
算
:
找
最
后
一
个
不
同
点
,
对
第
i
个
物
品
取
0
个
,
1
个
,
2
个
\begin{cases} 状态表示f[i][j]= {\begin{cases} 集合:所有只考虑前i种物品且总体积不超过j的方案的集合\\ 属性:取其中价值最大的方案 \end{cases}}\\ 状态计算:找最后一个不同点,对第 i 个物品取0个,1个,2个 \end{cases}
⎩⎪⎨⎪⎧状态表示f[i][j]={集合:所有只考虑前i种物品且总体积不超过j的方案的集合属性:取其中价值最大的方案状态计算:找最后一个不同点,对第i个物品取0个,1个,2个
取
0
0
0个
f
[
i
−
1
]
[
j
]
f[i-1][j]
f[i−1][j],取
1
1
1个
f
[
i
−
1
]
[
j
−
w
[
i
]
]
+
v
[
i
]
f[i-1][j-w[i]]+v[i]
f[i−1][j−w[i]]+v[i]
f
[
i
]
[
j
]
=
m
a
x
(
f
[
i
−
1
]
[
j
]
,
f
[
i
−
1
]
[
j
−
w
[
i
]
]
+
v
[
i
]
,
f
[
i
−
1
]
[
j
−
2
w
[
i
]
]
+
2
v
[
i
]
…
f
[
i
−
1
]
[
j
−
k
w
[
i
]
]
+
k
v
[
i
]
f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i],f[i-1][j-2w[i]]+2v[i]\\ \dots f[i-1][j-kw[i]]+kv[i]
f[i][j]=max(f[i−1][j],f[i−1][j−w[i]]+v[i],f[i−1][j−2w[i]]+2v[i]…f[i−1][j−kw[i]]+kv[i]
这样的转移是可以化简的
f
[
i
]
[
j
−
w
[
i
]
]
=
m
a
x
(
f
[
i
−
1
]
[
j
−
w
[
i
]
]
,
f
[
i
−
1
]
[
j
−
2
w
[
i
]
]
+
v
[
i
]
,
f
[
i
−
1
]
[
j
−
3
w
[
i
]
]
+
2
v
[
i
]
…
f
[
i
−
1
]
[
j
−
(
k
+
1
)
w
[
i
]
]
+
k
v
[
i
]
f[i][j-w[i]]=max(f[i-1][j-w[i]],f[i-1][j-2w[i]]+v[i],f[i-1][j-3w[i]]+2v[i]\\ \dots f[i-1][j-(k+1)w[i]]+kv[i]
f[i][j−w[i]]=max(f[i−1][j−w[i]],f[i−1][j−2w[i]]+v[i],f[i−1][j−3w[i]]+2v[i]…f[i−1][j−(k+1)w[i]]+kv[i]
带入上式即可得
f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i ] [ j − w [ i ] ] + v [ i ] f[i][j]=max(f[i-1][j],f[i][j-w[i]]+v[i] f[i][j]=max(f[i−1][j],f[i][j−w[i]]+v[i]
不优化空间
#include <cstdio>
#include <iostream>
#include <cstring>
#define ll long long
using namespace std;
const int maxn=100+10,maxm=1e7+10;
int n,m;
int c[1010],v[1010];
int dp[1010][1010];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;++i)
cin>>c[i]>>v[i];
for(int i=1;i<=n;++i)
{
for(int j=0;j<=m;++j)
{
dp[i][j]=dp[i-1][j];
if(j>=c[i])
dp[i][j]=max(dp[i][j],dp[i][j-c[i]]+v[i]);
}
}
cout<<dp[n][m]<<"\n";
return 0;
}
优化空间
#include <bits/stdc++.h>
using namespace std;
const int maxn=1010;
int n,m,w[maxn],v[maxn],f[maxn];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
scanf("%d%d",&w[i],&v[i]);
for(int i=1;i<=n;++i)
for(int j=w[i];j<=m;++j)
f[j]=max(f[j],f[j-w[i]]+v[i]);
printf("%d\n",f[m]);
return 0;
}
多重背包问题
思路:
{
状
态
表
示
f
[
i
]
[
j
]
=
{
集
合
:
所
有
只
考
虑
前
i
种
物
品
且
总
体
积
不
超
过
j
的
方
案
的
集
合
属
性
:
取
其
中
价
值
最
大
的
方
案
状
态
计
算
:
找
最
后
一
个
不
同
点
,
对
第
i
个
物
品
有
[
0
,
s
]
种
取
法
\begin{cases} 状态表示f[i][j]= {\begin{cases} 集合:所有只考虑前i种物品且总体积不超过j的方案的集合\\ 属性:取其中价值最大的方案 \end{cases}}\\ 状态计算:找最后一个不同点,对第 i 个物品有 [0,s]种取法 \end{cases}
⎩⎪⎨⎪⎧状态表示f[i][j]={集合:所有只考虑前i种物品且总体积不超过j的方案的集合属性:取其中价值最大的方案状态计算:找最后一个不同点,对第i个物品有[0,s]种取法
分析:其实也和01背包、完全背包没什么区别。只不过决策的次数变成了s次,物品、总体积、物品件数都在100以内
#include <bits/stdc++.h>
using namespace std;
const int maxn=110;
int n,m,w[maxn],v[maxn],s[maxn],dp[maxn][maxn];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
scanf("%d%d%d",&w[i],&v[i],&s[i]);
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
for(int k=0;k<=s[i]&&k*w[i]<=j;++k)
dp[i][j]=max(dp[i][j],dp[i-1][j-k*w[i]]+k*v[i]);
printf("%d\n",dp[n][m]);
return 0;
}
多重背包问题—二进制优化
思路:状态设置:
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示所有前i个物品总体积不超过 j 的方案的集合,然后取一个价值最大的方案
采用了二进制优化,因为有
s
s
s 件物品,用二进制可以表示
s
s
s 中的任意一个数
#include <bits/stdc++.h>
using namespace std;
int n,m,w,v,s;
int f[2010];
vector<pair<int,int> > vec;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
{
scanf("%d%d%d",&w,&v,&s);
for(int k=1;k<=s;k*=2)
{
s-=k;
vec.push_back({k*w,k*v});
}
if(s>0)
vec.push_back({s*w,s*v});
}
for(auto i : vec)
for(int j=m;j>=i.first;--j)
f[j]=max(f[j],f[j-i.first]+i.second);
printf("%d\n",f[m]);
return 0;
}
多重背包问题—单调队列优化
题意:有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
−
w
]
、
f
[
i
−
1
]
[
j
−
2
∗
w
]
、
…
、
f
[
i
−
1
]
[
j
−
s
∗
w
]
f[i-1][j-w]、f[i-1][j-2*w]、\dots、f[i-1][j-s*w]
f[i−1][j−w]、f[i−1][j−2∗w]、…、f[i−1][j−s∗w]转移过来的,因此我们可以根据余数 r 分组,每一组是:
r
、
r
+
w
、
r
+
2
w
、
…
r、r+w、r+2w、\dots
r、r+w、r+2w、…,维护一个最大值单调队列,每次先判断入队,然后缩减区间,最后更新
f
[
j
]
f[j]
f[j]
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e4+5,maxm=1e5+5;
const int mod=1e9+7,inf=0x7f7f7f7f;
int n,m;
int w[maxn],v[maxn],s[maxn];
int q[maxn],h,t;
int f[maxn],g[maxn];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
scanf("%d%d%d",&w[i],&v[i],&s[i]);
for(int i=1;i<=n;++i)
{
memcpy(g,f,sizeof(f));
for(int r=0;r<w[i];++r)
{
h=1,t=0;
for(int j=r;j<=m;j+=w[i])
{
while(h<=t&&g[q[t]]+(j-q[t])/w[i]*v[i]<=g[j])
t--;
q[++t]=j;
while(h<=t&&j-q[h]>s[i]*w[i])
h++;
f[j]=max(f[j],g[q[h]]+(j-q[h])/w[i]*v[i]);
}
}
}
printf("%d\n",f[m]);
return 0;
}