目录
一、对体积的限制
参考文章https://www.acwing.com/file_system/file/content/whole/index/content/1306630/
求方案数初始化
二维情况:
- 体积至多j,f(0,i)=1,i=0~m,其余是0。
- 体积恰好j,f(0,0)=1,其余是0。
- 体积至少j,f(0,0)=1,其余是0。
一维情况:
- 体积至多j,f(i)=1,i=0~m。
- 体积恰好j,f(0)=1,其余是0.
- 体积至少j,f(0)=1,其余是0
求最大最小值初始化总结
二维情况:
- 体积至多j,f(i,k)=0,i=0~n,k=0~m。(只能求最大值)
- 体积恰好j,
- 求价值最大值:f(0,0)=0,其余-INF
- 求价值最小值:f(0,0)=0,其余INF(只会从0,0与物品的体积和价值,特定的更新某些值)
- 体积至少j,f(0,0)=0,其余INF。(只能求最小值)
一维情况:
- 体积至多j,f全为0
- 体积恰好j,
- 求价值最大值,f(0)=0,其余-INF
- 求价值最小值,f(0)=0,其余INF
- 体积至少j,f(0)=0,其余INF
下面展示部分求价值最大值时,体积恰好j,体积至少j的代码,注意观察循环体内写法区别和意义,求体积至多j的问题不再赘述。
01背包体积恰好j
#include <iostream>
#include <cstring>
using namespace std;
const int N = 110, INF = 0x3f3f3f3f;
int n, m;
int f[N];
int main()
{
cin >> n >> m;
memset(f, -INF, sizeof f);
f[0] = 0;
for(int i = 1;i <= n;i ++)
{
int v, w;
cin >> v >> w;
for(int j = m;j >= v;j --)
{
f[j] = max(f[j], f[j - v] + w);
}
}
cout << f[m] << endl;
return 0;
}
作者:小呆呆
链接:https://www.acwing.com/file_system/file/content/whole/index/content/1306630/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
完全背包恰好j
#include <iostream>
#include <cstring>
using namespace std;
const int N = 110, INF = 0x3f3f3f3f;
int n, m;
int f[N];
int main()
{
cin >> n >> m;
memset(f, -INF, sizeof f);
f[0] = 0;
for(int i = 1;i <= n;i ++)
{
int v, w;
cin >> v >> w;
for(int j = v;j <= m;j ++)
{
f[j] = max(f[j], f[j - v] + w);
}
}
cout << f[m] << endl;
return 0;
}
作者:小呆呆
链接:https://www.acwing.com/file_system/file/content/whole/index/content/1306630/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
01背包至少j(只能求价值最小值)
不同点:恰好是j问题时,控制了j-v>=0,因为小于0的状态不存在,不可能由他们更新其它状态。而至少是j问题时,j-v<0的状态是合法的,等价于j-v==0,因此j层循环要遍历0~m,第二维状态写法为max(0,j-v)。
#include <iostream>
#include <cstring>
using namespace std;
const int N = 110, INF = 0x3f3f3f3f;
int n, m;
int f[N];
int main()
{
cin >> n >> m;
memset(f, INF, sizeof f);
f[0] = 0;
for(int i = 1;i <= n;i ++)
{
int v, w;
cin >> v >> w;
for(int j = m;j >= 0;j --)
{
f[j] = min(f[j], f[max(0, j - v)] + w);//即使物品体积比j大,j - v < 0,也能选,等价于f[0]
}
}
cout << f[m] << endl;
return 0;
}
作者:小呆呆
链接:https://www.acwing.com/file_system/file/content/whole/index/content/1306630/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
完全背包至少j(只能求价值最小值)
#include <iostream>
#include <cstring>
using namespace std;
const int N = 110, INF = 0x3f3f3f3f;
int n, m;
int f[N];
int main()
{
cin >> n >> m;
memset(f, INF, sizeof f);
f[0] = 0;
for(int i = 1;i <= n;i ++)
{
int v, w;
cin >> v >> w;
for(int j = 0;j <= m;j ++)
{
f[j] = min(f[j], f[max(0, j - v)] + w);//即使物品体积比j大,j - v < 0,也能选,等价于f[0]
}
}
cout << f[m] << endl;
return 0;
}
二、分组背包问题拓展
(1)Acwing 487
题目要求:要选择附件就必需选择主件。因此我们可以把每一个主件看成一个组,将不选择、选择1个附件、两个附件、、k个附件打包看做背包中的物品,则问题转化为分组背包问题。每组分为不选择,选择主和Cn0个附+Cn1个附+....共2^n种情况。
表示:可以用二进制数来表示打包的方案。例如:1010表示选择了第2个、第4个附件和主件。
#include<iostream>
#include<algorithm>
#include<cstring>
//每组分为不选择,选择主和Cn0个附+Cn1个附+....共2^n种情况,转化为分组背包问题
using namespace std;
const int N =70,M=32010;
typedef pair<int,int> PII;
PII master[N];
int n,m;
int f[M];
vector<PII> servent[N];
int main()
{
cin>>m>>n;
for(int i=1;i<=n;i++)
{
int v,w,q;
cin>>v>>w>>q;
if(!q) master[i]={v,v*w};
else servent[q].push_back({v,v*w});
}
for(int i=1;i<=n;i++)
{
if(master[i].first)
{
for(int j=m;j>=0;j--)
{
auto& sv=servent[i];
for(int k=0;k<1<<sv.size();k++)
{
int v=master[i].first,w=master[i].second;
for(int u=0;u<sv.size();u++)
if(k>>u&1)
{
v+=sv[u].first;
w+=sv[u].second;
}
if(j>=v) f[j]=max(f[j],f[j-v]+w);
}
}
}
}
cout<<f[m];
}
作者:yankai
链接:https://www.acwing.com/activity/content/code/content/4118462/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
(2)背包问题结合树、dfs(有依赖的背包问题)
三、混合背包问题
综合一下之前学过的,发现状态转移方程只和第i层的物品有关,和之前的物品种类无关。因此状态更新时用对应的转移方程即可。(这里用了一下代码简化版的2进制拆分解多重背包问题)
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N =1010;
int f[N];
int n,m;
//每种物品根据每种问题的状态转移方程做就可以,状态转移方程是独立的,不会因为其他物品的种类变化
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
int v,w,s;
cin>>v>>w>>s;
if(s==0)
{
for(int j=v;j<=m;j++)
f[j]=max(f[j],f[j-v]+w);
}
else
{
if(s==-1) s=1;
for(int k=1;k<=s;k*=2)
{
for(int j=m;j>=k*v;j--)
f[j]=max(f[j],f[j-k*v]+k*w);
s-=k;
}
if(s)
{
for(int j=m;j>=s*v;j--)
f[j]=max(f[j],f[j-s*v]+s*w);
}
}
}
cout<<f[m];
return 0;
}
作者:yankai
链接:https://www.acwing.com/activity/content/code/content/4118828/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
四、背包问题求最优方案数
状态表示为体积恰好是j时,f表示最优方案的取值,g表示最优方案的数量。
f的状态转移方程不再赘述,g的状态转移方程分为两部分,不选第i件物品与选择第i件物品。如果不选是最优解,则g[j]=g[j],如果选第i件物品是最优解,则g[j]=g[j-v],如果两者相等,则g[j]=g[j]+g[j-v]。
最后遍历f求得全局最优解的值,然后遍历g,求得全局最优解方案的数量
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N =1010,mod=1e9+7;
int f[N],g[N];
int n,m;
int main()
{
cin>>n>>m;
//状态表示为体积恰好是j
memset(f,0xcf,sizeof f);
f[0]=0;
g[0]=1;
for(int i=1;i<=n;i++)
{
int v,w;
cin>>v>>w;
for(int j=m;j>=v;j--)
{
int cnt=0;
int maxv=max(f[j],f[j-v]+w);
if(maxv==f[j]) cnt+=g[j];
if(maxv==f[j-v]+w) cnt+=g[j-v];
g[j]=cnt%mod;
f[j]=maxv;
}
}
int res=0;
//由于状态表示是恰好,所以要遍历求出最优方案所得的值,然后再求最优方案出现的次数
for(int i=0;i<=m;i++) res=max(res,f[i]);
int cnt=0;
for(int i=0;i<=m;i++)
if(res==f[i])
cnt=(cnt+g[i])%mod;
cout<<cnt;
return 0;
}
作者:yankai
链接:https://www.acwing.com/activity/content/code/content/4119420/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
五、背包问题结合贪心
直觉发现,如果前面吃能量石用过多时间,后面能量就会流失严重,因此选择时应该遵循某种顺序选择。
接下来假设有i,j两块能量石,探讨一下根据什么顺序选择。
如果按i,j顺序吃:
如果按j,i顺序吃:
因此选择顺序由s/l决定,s/l越小,后面能量流失就越小。
按s/l排序后,转化为01背包问题
为了计算损失的能量,状态表示为体积恰好等于j,状态转移方程为:
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N =1010,mod=1e9+7;
int f[N],g[N];
int n,m;
int main()
{
cin>>n>>m;
//状态表示为体积恰好是j
memset(f,0xcf,sizeof f);
f[0]=0;
g[0]=1;
for(int i=1;i<=n;i++)
{
int v,w;
cin>>v>>w;
for(int j=m;j>=v;j--)
{
int cnt=0;
int maxv=max(f[j],f[j-v]+w);
if(maxv==f[j]) cnt+=g[j];
if(maxv==f[j-v]+w) cnt+=g[j-v];
g[j]=cnt%mod;
f[j]=maxv;
}
}
int res=0;
//由于状态表示是恰好,所以要遍历求出最优方案所得的值,然后再求最优方案出现的次数
for(int i=0;i<=m;i++) res=max(res,f[i]);
int cnt=0;
for(int i=0;i<=m;i++)
if(res==f[i])
cnt=(cnt+g[i])%mod;
cout<<cnt;
return 0;
}
作者:yankai
链接:https://www.acwing.com/activity/content/code/content/4119420/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。