题目 | 类型 |
---|---|
1、混合背包问题 | 混合背包问题 |
2、机器分配 | 背包问题方案数量 |
3、背包问题求方案数 | 背包问题方案数量 |
1、混合背包问题
有 N 种物品和一个容量是 V 的背包。
物品一共有三类:
第一类物品只能用1次(01背包); 第二类物品可以用无限次(完全背包); 第三类物品最多只能用 si 次(多重背包); 每种体积是 vi ,价值是 wi 。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。 输出最大价值。
输入格式
第一行两个整数,N,V ,用空格隔开,分别表示物品种数和背包容积。接下来有 N 行,每行三个整数 vi,wi,si ,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
si=−1 表示第 i 种物品只能用1次; si=0 表示第 i 种物品可以用无限次; si>0 表示第 i 种物品可以使用 si 次;
输出格式
输出一个整数,表示最大价值。数据范围
0<N,V≤1000
0<vi,wi≤1000
−1≤si≤1000
输入样例
4 5
1 2 -1
2 4 1
3 4 0
4 5 2
输出样例:
8
思路:
完全背包问题可以转化为多重背包问题,这是本题的关键所在,完全背包能用无数次,等价于可以一种物品用到背包满为止,所以我们有/转化公式:
s[i]=m/v[i];
将完全背包转化为多重背包
另外,本题采用二进制优化来加快程序运行速度,二进制优化打包后就相当于01背包问题
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int v[N],w[N],s[N];
int chmodv[N];
int chmodw[N];
int f[N];
//将完全背包转化为多重背包(能取无数次相当于有背包体积/v个物品)
//-1 01 0 完全 >0 多重
int n,m;//m是背包容积
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>v[i]>>w[i]>>s[i];
if(s[i]==-1)s[i]=1;//01背包转化为多重背包
else if(s[i]==0)s[i]=m/v[i];//完全背包转化为多重背包
}
//二进制优化(朴素写法要三重循环,在第三重循环枚举s[i])
//二进制优化后,多个物品被打包,相当于01背包问题
//所以只需要01背包的做法(二重循环)
int cnt=1;
for(int i=1;i<=n;i++)
{
int k=1;//用来二进制打包
while(k<=s[i])
{
chmodv[cnt]=v[i]*k;
chmodw[cnt]=w[i]*k;
s[i]-=k;
k*=2;
cnt++;
}
if(s[i]>0)
{
chmodv[cnt]=s[i]*v[i];
chmodw[cnt]=s[i]*w[i];
cnt++;
}
}///二进制优化完成
// 朴素写法
// for(int i=1;i<=cnt;i++)//这时候是多个物品打包在一起,做法和01背包一样
// {
// for(int j=0;j<=m;j++)
// {
// f[i][j]=f[i-1][j];
// if(j>=chmodv[i])f[i][j]=max(f[i][j],f[i-1][j-chmodv[i]]+chmodw[i]);
// }
//
// }
// 滚动数组写法(可以储存更多的状态,如果是二维存储的状态不够用)
for(int i=1;i<=cnt;i++)
{
for(int j=m;j>=chmodv[i];j--)
{
f[j]=max(f[j],f[j-chmodv[i]]+chmodw[i]);
}
}
cout<<f[m];
return 0;
}
2、机器分配
总公司拥有 M 台 相同 的高效设备,准备分给下属的 N 个分公司。
各分公司若获得这些设备,可以为国家提供一定的盈利。盈利与分配的设备数量有关。
问:如何分配这M台设备才能使国家得到的盈利最大?
求出最大盈利值。
分配原则:每个公司有权获得任意数目的设备,但总台数不超过设备数 M 。
输入格式
第一行有两个数,第一个数是分公司数 N ,第二个数是设备台数 M ;接下来是一个 N×M 的矩阵,矩阵中的第 i 行第 j 列的整数表示第 i 个公司分配 j 台机器时的盈利。
输出格式 第一行输出最大盈利值;
接下 N 行,每行有 2 个数,即分公司编号和该分公司获得设备台数。
答案不唯一,输出任意合法方案即可。
数据范围
1≤N≤10 , 1≤M≤15
输入样例:
3 3
30 40 50
20 30 50
20 25 30
输出样例:
70 1 1
2 1 3 1
思路:
每一个公司可以看作一个组,从每一组中选
最终用dfs寻找路径:!!从最终状态开始dfs!!倒推到初始状态
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=12,M=17;
int n,m;//公司数量和机器数量
int g[N][M];
int f[N][M];
int path[N];
int cnt;
void dfs(int i,int j)
{
if(i==0)return ;
for(int a=0;a<=j;a++)
{
if(f[i-1][j-a]+g[i][a]==f[i][j])
{
path[cnt++]=a;
dfs(i-1,j-a);
return ;
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
scanf("%d",&g[i][j]);//读入数据矩阵
}
//每个公司可以看作一个组,从每一组中选
for(int i=1;i<=n;i++)//从前i组里面选
{
for(int j=1;j<=m;j++)//枚举体积(总共m台机器)
{
for(int k = 0;k <= j;k ++)
{
f[i][j]=max(f[i][j],f[i-1][j]);
f[i][j]=max(f[i][j],f[i-1][j-k]+g[i][k]);
//g[i][k]可以解读为第i组中体积为k,价值为g[i][k]
}
}
}
cout<<f[n][m]<<endl;
//find path
dfs(n,m);
//因为是从最终状态开始find拓扑排序的
//所以cnt==0的时候存储的是最终的状态
//最终path要倒序输出
for(int i=cnt-1,id=1;i>=0;id++,i--)
cout<<id<<" "<<path[i]<<endl;
}
3、背包问题求方案数
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi ,价值是 wi 。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出 最优选法的方案数。注意答案可能很大,请输出答案模 109+7 的结果。
输入格式
第一行两个整数,N,V ,用空格隔开,分别表示物品数量和背包容积。接下来有 N 行,每行两个整数 vi,wi ,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示 方案数 模 109+7 的结果。数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 6
输出样例:
2
思路:
cnt[i]存储价值为i的时候的方案数
相当于进行两次dp
代码:
#include<bits/stdc++.h>
using namespace std;
//01背包问题求方案数
//f[i]存储体积为i的时候的最高价值
//cnt[i]存储体积为i的时候的最高价值(f[i])对应的方案数
const int N=1003;
int f[N];
int cnt[N];
int v[N],w[N];
int mod=1e9+7;
int main()
{
int n,m;
cin>>n>>m;
for(int i = 0; i <= m; i ++) cnt[i] = 1;//初始值全部设置为1
//因为最高价值都可以为0,方案就可以有一种
for(int i=1;i<=n;i++)
{
scanf("%d%d",&v[i],&w[i]);
}
for(int i=1;i<=n;i++)
for(int j=m;j>=v[i];j--)
{
int value=f[j-v[i]]+w[i];
if(value>f[j])//找到价值更高的方案
{
f[j]=value;
cnt[j]=cnt[j-v[i]];
}
else if(value==f[j])
{
cnt[j]=(cnt[j]+cnt[j-v[i]])%mod;
}
}
cout<<cnt[m];
return 0;
}