AcWing
  • 首页
  • 课程
  • 题库
  • 更多
    • 竞赛
    • 题解
    • 分享
    • 问答
    • 应用
    • 校园
  • 关闭
    历史记录
    清除记录
    猜你想搜
    AcWing热点
  • App
  • 登录/注册

AcWing 12. 背包问题求具体方案【01背包 + 背包DP输出方案】    原题链接    中等

作者: 作者的头像   一只野生彩色铅笔 ,  2021-06-25 17:10:17 ,  所有人可见 ,  阅读 7838


68


22

最近在补全提高课所有题目的题解,宣传一下汇总的地方提高课题解补全计划

题目描述

有 $N$ 件 物品 和一个 容量 为 $V$ 的 背包

第 $i$ 件物品的 体积 是$v_i$, 价值 是$w_i$,每件 物品 只能使用一次

求解一种 方案,使得选择的 物品 的 总体积 不超过背包的 最大容量,且 总价值 最大

输出 字典序最小的方案

分析

本题是 背包DP 的经典变种模型 背包DP输出方案

输出方案 其实就是输出方案的 转移路径

我们先做一遍正常的 背包DP ,然后从 目标状态 倒推回 初始状态 的整个 转移路径 即可

说的白话一点就是,在考虑第 i 件物品时,选择了 选 还是 不选 的策略到达了第 i+1 件物品

伪代码如下:(参考oi wiki)

int v = V;  // 记录当前的存储空间

// 因为最后一件物品存储的是最终状态,所以从最后一件物品进行循环
for (从最后一件循环至第一件)
{
  if (g[i][v])
  {
    选了第 i 项物品;
    v -= 第 i 项物品的价值;
  } else
    未选第 i 项物品;
}

当然也可以从 拓扑图 的角度来 分析理解,这次不额外展开,具体可以参考下面这篇博客,里面也有 DFS 的写法

AcWing 1013. 机器分配【分组背包+背包DP输出方案—拓扑图分析】

这里主要展示 迭代 的写法(其实就是在跟踪状态转移的路径)

题目里还要求了 输出 字典序最小的方案

而在倒推 状态转移路径 的时候,只能在 分叉转移 的时候,即 当前 物品既可以 选 又可以 不选 时,优先 选

因此,我们本题的 背包DP 需要倒过来(从N递推到1)做,然后再 从1倒推回N 找出路径

这样在抉择时,如果出现 分叉转移,我们就优先 选 当前物品即可

01背包模型 $\mathrm{f[i][j]}$

状态表示$f(i,j)$—集合: 考虑后 i 个物品,且当前已使用体积不超过 j 的方案

状态表示$f(i,j)$—属性: 该方案的价值为最大值 $\max$

状态转移$f(i,j)$: $f(i,j) = \begin{cases} 不选第i个物品: &\max\{f(i+1,j)\} \\\ 选第i个物品:&\max\{f(i+1,j - v_i) + w_i\} \end{cases}$

Code

时间复杂度:$O(N \times V)$

路径追踪的 递归 写法可以参考这篇博客
AcWing 1013. 机器分配【分组背包+背包DP输出方案—拓扑图分析】

#include <iostream>

using namespace std;

const int N = 1010;

int n, m;
int w[N], v[N];
int f[N][N];
int path[N], cnt;

int main()
{
    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]);
        }
    }
    for (int i = 1, j = m; i <= n; ++ i)
    {
        if (j >= v[i] && f[i][j] == f[i + 1][j - v[i]] + w[i])
        {
            path[cnt ++ ] = i;
            j -= v[i];
        }
    }
    for (int i = 0; i < cnt; ++ i) cout << path[i] << " ";
    cout << endl;
    return 0;
}


16 评论


用户头像
醒了不起的盖兹比   2023-04-26 00:53      7    踩      回复

立志走遍铅笔哥每一篇题解


用户头像
Catw1thu   2023-03-10 23:10      4    踩      回复

感觉这里的f(i,j)不是考虑后i个物品,而是考虑第i个物品到第n个物品

用户头像
糖豆   2023-05-17 08:50         踩      回复

你是对的,举个栗子: 1 2 3 4 5 6 7 8 9 10,如果是从前往后到第5个,f(5,j)就是正常的前5个,如果是从后往前就是:10 9 8 7 6 5,这其实是6个。


用户头像
awdasfasfa   2024-02-04 16:21         踩      回复

为什么一维数组错了
#include [HTML_REMOVED]
using namespace std;
const int N = 1010;
int n, m;
int w[N], v[N];
int f[N];
int path[N], cnt;
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i) cin >> v[i] >> w[i];
for (int i = 1; i <=n;
i)
{
for (int j = m; j >= v[i]; j–)
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
for (int i = 1, j = m; i <= n; i)
{
if (j >= v[i] && f[j] == f[j - v[i]] + w[i])
{
path[cnt
] = i;
j -= v[i];
}
}
for (int i = 0; i < cnt; ++ i) cout << path[i] << ” “;
return 0;
}

用户头像
阿飞_14   2025-03-21 17:07 · 江西         踩      回复

因为你下面输出序列的dp无法满足上一层的一维是结果化的,二维才能表示过程,一维的话要这样做#include[HTML_REMOVED]
using namespace std;

const int N = 1005;

int dp[N];
//体积为j的最优解的序列
vector[HTML_REMOVED] g[N];

int main(){
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);

int n, v;
cin >> n >> v;

for(int i = 1; i <= n; i++) {
    int vi, wi;
    cin >> vi >> wi;
    for(int j = v; j >= vi; j--) {
        if(dp[j] < dp[j - vi] + wi) {
            dp[j] = dp[j - vi] + wi;
            g[j] = g[j - vi]; 
            g[j].push_back(i); 
        } else if(dp[j] == dp[j - vi] + wi) {
            vector<int> temp = g[j - vi];
            temp.push_back(i);
            if(g[j] > temp) {
                g[j] = temp; 
            }
        }
    }
}
for(int i = 0; i < g[v].size(); i++) {
    cout << g[v][i] << " ";
}
cout << endl;

return 0;

}


用户头像
xzqq   2022-08-01 21:46         踩      回复

想问一下为什么这里的dp状态转移方程j的下标从0 到小于等于m呢

用户头像
qbz666   2022-09-27 16:46         踩      回复

因为是朴素版,最本质的就是从下标0开始,一维优化后是从v[i]开始

用户头像
未至之境   2022-11-10 19:45         踩      回复

无所谓啦 只要能够枚举所有状态就行 从1枚举是一样的
因为j=0时背包容量为0 没有办法选物品 所以f[i][0]=0
又因为我们定义的是全局变量 所以本身就是0 没有什么影响的hh


用户头像
蓝色一定是最幸福的颜色   2021-12-01 15:06         踩      回复

4 5
1 2
2 2
2 2
4 4
请问博主如果这样写的话这组数据还能表示字典序最小吗

用户头像
Cplusplus   2023-03-18 16:53         踩      回复

是吧


用户头像
Laurus_1   2021-10-08 10:01         踩      回复

求具体方案那么多方法用哪个比较好啊,是大佬写的机械分配那种吗

用户头像
一只野生彩色铅笔   2021-10-08 13:56         踩      回复

在转移方程复杂的时候,递归会好写很多


用户头像
风之羁绊   2021-06-25 17:24         踩      回复

1449题


用户头像
风之羁绊   2021-06-25 17:24         踩      回复

leetcode有一道题,使用完全背包求具体方案,可以用一维数组的形式来写,能分析下,求具体方案时,为什么01背包不能忽略物品的这维空间?

用户头像
一只野生彩色铅笔   2021-06-25 18:22      1    踩      回复

我看了一下官方题解的做法大概动你想表达什么了

官方题解 数位DP + 背包DP 的结合做法

他的 状态表示 是:
集合:考虑前 i 个数,当前体积恰好为 j 的方案
属性:方案的数字的位数

因此在最后倒推的时候,他并没有像 传统DP 那样找 状态转移路径

相反,他用的是 贪心 思路
从大到小枚举,如果这个数选了这么多数位后,减掉选的个数
再考虑前几个数,如果存在一种方案,则说明选这么多数的方案是可以达到最优解的

用户头像
一只野生彩色铅笔   2021-06-25 18:23    回复了 一只野生彩色铅笔 的评论         踩      回复

这个贪心是可以证明的,所以只需利用最后一层的状态即可


App 内打开
你确定删除吗?
1024
x

© 2018-2025 AcWing 版权所有  |  京ICP备2021015969号-2
用户协议  |  隐私政策  |  常见问题  |  联系我们
AcWing
请输入登录信息
更多登录方式: 微信图标 qq图标 qq图标
请输入绑定的邮箱地址
请输入注册信息