文章目录
- 求方案数
- 基本思路
- 代码
- 背包问题求具体方案
- 基本思路
- 代码
- 有依赖背包
- 基本思路
- 代码
求方案数
问题描述:
给定n nn个物品,以及一个容量大小为m mm的背包,然后给出n nn个物品的体积及价值,求背包最大价值是多少,也就是选择总体积不超过m mm的物品,然后使总价值最大。然后输出最优解的方案数。
基本思路
这是一个完全背包问题,可以使用动态规划来解决。
设dpi表示凑出体积为 i 的方案数,则有状态转移方程
边界条件为dp0=1因为背包体积为 0 时,不选任何物品即为一种方案。
最终答案即为 dpm表示凑出体积为 m 的方案数。
下面以一个样例来进行模拟解释:
假设给定物品体积数组为 v = [ 1 , 2 , 3 ]背包体积为 m = 5 则可以得到如下的状态转移方程:
代码
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
vector<int> v(n+1);
for (int i = 1; i <= n; ++i) {
cin >> v[i];
}
vector<int> dp(m+1);
dp[0] = 1; // 背包体积为0时,方案数为1
for (int i = 1; i <= n; ++i) {
for (int j = v[i]; j <= m; ++j) {
dp[j] += dp[j - v[i]];
}
}
cout << dp[m] << endl; // 输出恰好凑出体积为m的方案数
return 0;
}
背包问题求具体方案
题目描述:
给定n 个物品,以及一个容量大小为m 的背包,然后给出n个物品的体积及价值,求背包最大价值是多少,也就是选择总体积不超过m 的物品,然后使总价值最大。然后输出最优解的方案,并输出字典序最小的最优方案。
基本思路
注意,因为要求输出的顺序是按照字典序,故我们在进行 dp的过程中,枚举样本需要从大到小枚举,这样我们在按照字典序输出的时候就可以正向循环,查看状态 i是由哪个状态 j转移而来的 ( j > i )
代码
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1010;
int v[N], w[N];
int f[N][N];
int main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i];
for (int i = n; i >= 1; i -- )
for (int j = 0; j <= m; j ++ )
{
f[i][j] = f[i + 1][j];
if (j >= v[i]) f[i][j] = max(f[i][j], f[i + 1][j - v[i]] + w[i]);
}
int j = m;
for (int i = 1; i <= n; i ++ )
if (j >= v[i] && f[i][j] == f[i + 1][j - v[i]] + w[i])
{
cout << i << ' ';
j -= v[i];
}
return 0;
}
有依赖背包
题目描述
有N 个物品和一个容量是V 的背包。物品之间具有依赖关系,且依赖关系组成一棵树的形状。如果选择一个物品,则必须选择它的父节点。
给出物品的体积、价值和依赖的物品编号,并保证所有物品组成一棵树,依赖的物品编号为-1则表示为根节点。
基本思路
有依赖关系的背包问题,他的每一个物品的选择情况都有其子节点的选择情况构成,而子节点的选择情况又由父节点决定(父节点如果不选,则所有子节点都选不了)。我们将每个父节点及其子节点看作是一个分组,父节点的情况由子节点所有选择情况中的一种构成,然后我们就成功的将该问题转化为了一道分组背包问题。
我们定义f[i][j]数组,i 表示节点,j 表示所占容量,那么f[i][j]就表示i节点在j 容量的情况下的最大价值。现在能和分组背包联系起来了吗?感觉没有关联也没问题,再接着往下看。在分组背包问题中,我们的第三维枚举决策的时候是枚举当前组里的物品,并且只能选一个物品。
而在此问题中,我们将每棵子树看作一个分组,将父节点的所有子节点的状态看作是组内的物品,枚举子节点所有选择情况所占体积,因为不同体积代表子节点的不同选择状态。然后我们从根节点开始递归到叶子节点,然后从叶子节点开始选择,每个节点的选择情况枚举完之后再返回到其父节点继续选择,最终返回到根节点进行选择。最后f[root][m]即为以root为根节点的树在容量为m情况下的最大价值。但需要注意,此时代表的时容量小于等于m的最大价值,同基础01背包一样,不是代表容量刚好为m时的最大价值。
代码
#include<iostream>
using namespace std;
int N,V,p,root;//N个物品V背包容量
int v[105],w[105];
int a[105][105],b[105],f[105][105];
//a[i][j]表示以i为头结点有j个子节点,a[i][j]则存的是下标,b[i]表示以i为根结点有b[i]个子节点
//f[i][j]表示以i为根节点,背包容量为j所获得的最大价值
void dfs(int t){//有树就要考虑遍历用dfs深搜,t表示此时父节点
//此时t为父节点,要想选下面的,前提就是把父节点选了,所以初始背包容量大于v[t]都初始化w[t]
for(int i=v[t];i<=V;i++){
f[t][i]=w[t];
}
//下面不是一个父节点有许多子节点,按个遍历初始化它们,那么身为子节点又是父节点,又有子节点,递归下去
for(int i=0;i<b[t];i++){
int s=a[t][i];
dfs(s);
for(int j=V;j>=v[t];j--){//类似01背包逆序遍历
for(int k=0;k<=j-v[t];k++){
f[t][j]=max(f[t][j],f[t][j-k]+f[s][k]);//状态转移方程
//f[t][j-k]+f[s][k]表示父节点要j-k的容量,子节点要k的容量
}
}
}
}
int main(){
cin>>N>>V;
for(int i=1;i<=N;i++){
cin>>v[i]>>w[i]>>p;
if(p==-1){//-1表示为根节点
root=i;
}else{
a[p][b[p]++]=i;//可以看一下上面a[i][j]与b[i]的含义
}
}
dfs(root);
cout<<f[root][V]<<endl;
return 0;
}