考前复习——背包问题
01,多重,完全,以及混合背包
对于这种类型的背包问题 我采用的策略是——全都当多重背包来做
那么代码看起来会是这个样子
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define Inf 0x3f3f3f3f
#define N 1005
#define M 505
#define re register
int n,m;
int w[N],c[N];
int cnt;
int f[M];
int main()
{
/*
混合背包——'简单'的变种
处理一下01 多重 完全的识别即可
事实上 选一次 多次 无数次 都是算'多次'嘛
*/
cin>>m>>n;
int a,s,d;
for(int i=1;i<=n;i++)
{
scanf("%d%d%d",&a,&s,&d);//体积 价值 几个
//把d(物品数量)拆成2进制表示
int k=1;
if(d==0)d=m;//这里的d==0代表物品无限的情况(即使该物品体积为1 也最多在背包里装m件)
while(k<=d)
{
cnt++;
w[cnt]=a*k;
c[cnt]=s*k;
d-=k;
k*=2;
}
if(d>0)
{
cnt++;
w[cnt]=a*d;
c[cnt]=s*d;
}
}
n=cnt;
for(int i=1;i<=n;i++)
{
for(int j=m;j>=w[i];j--)//在二进制优化之后 便类似于01背包 逆序求解
{
f[j]=max(f[j],f[j-w[i]]+c[i]);
}
}
cout<<f[m];
return 0;
}
分组背包
分组背包其实是从「在所有物品中选择一件」变成了「从当前组中选择一件」,于是就对每一组进行一次 0-1 背包就可以了。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define T 15
#define N 1005
#define M 1005
#define re register
int n,m,t;
int w[T][N],c[T][N],cnt[T];
int f[M];
int main()
{
cin>>m>>n>>t;//容量 物品件数 组数
int a,s,d;
for(re int i=1;i<=n;i++)
{
cin>>a>>s>>d;//分组记录
w[d][++cnt[d]]=a;
c[d][cnt[d]]=s;
}
// for(int i=1;i<=t;i++)
// cout<<cnt[i]<<endl;
for(re int i=1;i<=t;i++)
{
for(re int j=m;j>=0;j--)
{
for(re int k=1;k<=cnt[i];k++)//第i组的第k个物品
{
if(j>=w[i][k])
f[j]=max(f[j],f[j-w[i][k]]+c[i][k]);
}
}
}
cout<<f[m];
return 0;
}
有依赖的背包问题
金明有 n 元钱,想要买 m 个物品,第 i 件物品的价格为 v_i,重要度为 p_i。有些物品是从属于某个主件物品的附件,要买这个物品,必须购买它的主件。
目标是让所有购买的物品的 v_i * p_i 之和最大。
那么对于以某个主件为主的物品组 只有两种可能:只买该主件,或买主件和其他某些附件。
这两种可能相当于分组背包中的一个组 你只需要在这其中做一次01 得到该物品组的最优 最后取总最优即可
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define N 32005
#define M 65
int n,m;
int m_w[M],m_v[M];
int sum[M];
int a_w[M][3],a_v[M][3];
long long dp[N];
int main()
{
/*
有依赖的背包问题——以洛谷p1064为例
所有的物品由若干主件和依赖于每个主件的一个附件集合组成
考虑对于每一个主件为首的物品组
对所有物品组做出最优决策后 只需考虑将所有物品组再归为一个物品组
再次求解即可
即 如果直接用分组背包的方法求解 有很多决策是冗余的
(对于i-1件物品的决策将i的决策重复考虑)
*/
cin>>n>>m;//m件物品 背包容量n
int w,v,p;
for(int i=1;i<=m;i++)
{
cin>>w>>v>>p;//物品价格 重要度 附属于哪组
//在该题目中 一个物品的 '体积' 为价格 '价值' 为价格*重要度
if(p)
{
//不为零则附属于p
sum[p]++;
a_w[p][sum[p]]=w;
a_v[p][sum[p]]=w*v;
}
else
{
//为0则主件
m_w[i]=w;
m_v[i]=w*v;
}
}
for(int i=1;i<=m;i++)
for(int j=n;m_w[i]!=0&&j>=m_w[i];j--)
{
//这里是对于一个以主件为首的物品组进行01求解
//本题至多有2件附件 故直接分类讨论
dp[j]=max(dp[j],dp[j-m_w[i]]+m_v[i]);
if(j>=m_w[i]+a_w[i][1])
dp[j]=max(dp[j],dp[j-m_w[i]-a_w[i][1]]+m_v[i]+a_v[i][1]);
if(j>=m_w[i]+a_w[i][2])
dp[j]=max(dp[j],dp[j-m_w[i]-a_w[i][2]]+m_v[i]+a_v[i][2]);
if(j>=m_w[i]+a_w[i][1]+a_w[i][2])
dp[j]=max(dp[j],dp[j-m_w[i]-a_w[i][1]-a_w[i][2]]+m_v[i]+a_v[i][1]+a_v[i][2]);
}
cout<<dp[n];
return 0;
}
背包问题的更多变种
- 输出方案
对于要求输出方案的问题 只需要不断考虑‘当前状态是由哪一个决策引起的’即可
以01背包为例
int w=m;//最终背包被装入的体积
for(int i=n;i>0;i--)
{
if(f[i][w]==f[i-1][w-c[i]]+v[i])
{
//装了第i件物品
v-=c[i];
}
else if(f[i][w]==f[i-1][w])
{
//没装第i件物品
}
}
- 将背包装至某一容量
注意到在背包问题求解时 已经考虑到了所有状态
因此其它问题的求解可以在此基础上进行
如‘将背包装至某一容量 求有多少方案’这类问题
f[j]=f[j]/*不装方案数*/+f[j-w[i]]/*装了的方案数*/
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define N 5005
#define M 5005
#define ll long long
#define re register
ll m,n;
ll w[N],c[N];
ll f[M];
int main()
{
cin>>n>>m;
for(re int i=1;i<=n;i++)
{
cin>>w[i];
}
f[0]=1;//什么都不装 也是一种方案
for(re int i=1;i<=n;i++)
{
for(re int j=w[i];j<=m;j++)//这里为顺序(完全背包)的做法
{
f[j]=f[j]+f[j-w[i]];
}
}
cout<<f[m];//填装至m
return 0;
}
- 求最优方案的方案总数
为了对这个问题求解 需要将01背包中的f[][]的定义进行修改
DP 状态 f[i][j] 为在只能放前 i 个物品的情况下,容量为 j 的背包「正好装满」所能达到的最大总价值。
这样修改之后,每一种 DP 状态都可以用一个 g[i,j] 来表示方案数。
f[i,j] 表示只考虑前 i 个物品时背包体积「正好」是 j 时的最大价值。
g[i,j] 表示只考虑前 i 个物品时背包体积「正好」是 j 时的方案数。
转移方程:
如果 f_{i,j} = f_{i-1,j} 且 f_{i,j} ≠ f_{i-1,j-v}+w 说明我们此时不选择把物品放入背包更优,方案数由 g_{i-1,j} 转移过来,
如果 f_{i,j} ≠ f_{i-1,j} 且 f_{i,j} = f_{i-1,j-v}+w 说明我们此时选择把物品放入背包更优,方案数由 g_{i-1,j-v} 转移过来,
如果 f_{i,j} = f_{i-1,j} 且 f_{i,j} = f_{i-1,j-v}+w 说明放入或不放入都能取得最优解,方案数由 g_{i-1,j} 和 g_{i-1,j-v} 转移过来。
初始条件:
memset(f, 0x3f3f, sizeof(f)); // 避免没有装满而进行了转移
f[0] = 0;
g[0] = 1; // 什么都不装是一种方案
因为背包体积最大值有可能装不满,所以最优解不一定是 f[m]。
最后我们通过找到最优解的价值,把 g[j] 数组里取到最优解的所有方案数相加即可。
点击查看代码
for (int i = 0; i < n; i++) {
for (int j = V; j >= v[i]; j--) {
int tmp = max(dp[j], dp[j - v[i]] + w[i]);
int c = 0;
if (tmp == dp[j]) c += cnt[j]; // 如果从dp[j]转移
if (tmp == dp[j - v[i]] + w[i]) c += cnt[j - v[i]]; // 如果从dp[j-v[i]]转移
dp[j] = tmp;
cnt[j] = c;
}
}
int max = 0; // 寻找最优解
for (int i = 0; i <= n; i++) {
max = max(max, dp[i]);
}
int res = 0;
for (int i = 0; i <= V; i++) {
if (dp[i] == max) {
res += cnt[i]; // 求和最优解方案数
}
}
本文来自博客园,作者:pig_pig,转载请注明原文链接:https://www.cnblogs.com/pigAlg/p/17485497.html
浙公网安备 33010602011771号