背包九讲(求方案数,求具体方案数,有依赖背包)

求方案数

问题描述:
给定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;
}
  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值