0x52. 动态规划 - 背包(习题详解 × 19)

Posted 繁凡さん

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了0x52. 动态规划 - 背包(习题详解 × 19)相关的知识,希望对你有一定的参考价值。

本系列博客是《算法竞赛进阶指南》的学习笔记,包含书中的部分重要知识点、例题解题报告及我个人的学习心得和对该算法的补充拓展,仅用于学习交流和复习,无任何商业用途。博客中部分内容来源于书本和网络 ,由我个人整理总结。部分内容由我个人编写而成,如果想要有更好的学习体验或者希望学习到更全面的知识,请于京东搜索购买正版图书:《算法竞赛进阶指南》——作者李煜东,强烈安利,好书不火系列,谢谢配合。
%
学习笔记目录链接: 学习笔记目录链接
%
整理的算法模板合集: ACM模板
%
点我看算法全家桶系列!!!


0x52. 动态规划 - 背包

0x52.1 0 / 1 0/1 0/1 背包

Template

给定 N N N 个物品,其中第 i i i 个物品的体积为 V i V_i Vi,价值为 W i W_i Wi,有一个容积为 M M M 的背包, 求选择一些物品放入背包,使得物品的总体积不超过 M M M 的前提下,物品的价值总和最大,即背包不一定装满。

Solution

显然是一个线性DP,我们选择已处理的物品数和当前总体积作为DP的阶段
f [ i , j ] f[i, j] f[i,j] 从前 i i i 个物品中选出总体积 j j j 物品放入背包的最大价值。

f [ i ] [ j ] = f [ i − 1 ] [ j ] f[i][j] = f[i - 1][j] f[i][j]=f[i1][j] 不选择第 i i i 个物品
f [ i ] [ j ] = max ⁡ { f [ i ] [ j − v [ i ] ] + w [ i ] } f[i][j] = \\max\\{f[i][j - v[i]] + w[i]\\} f[i][j]=max{f[i][jv[i]]+w[i]} 选择第 i i i 个物品

初始化 f [ 0 , 0 ] = 0 f[0, 0] = 0 f[0,0]=0

可以写出暴力转移代码:

f[maxn][maxn];
memset(f, 0xcf, sizeof f);

f[0][0] = 0;
for (int i = 1; i <= n; ++ i) {
	for (int j = 0; j <= m; ++ j)
		f[i][j] = f[i - 1][j];
	for (int j = v[i], j <= m; ++ j)
		f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
} 

由于 f [ i ] [ ] f[i][] f[i][] 仅与 f [ i − 1 ] [ ] f[i -1][] f[i1][] 有关,显然可以使用滚动数组优化。
由于转移方程中 f [ i ] [ j ] = f [ i − 1 ] [ j ] f[i][j] = f[i - 1][j] f[i][j]=f[i1][j] ,每次需要做一次拷贝,因此在做滚动数组的时候不需要清空。

f[2][maxn];

for (int i = 1; i <= n; ++ i) {
	for (int j = 0; j <= m; ++ j)
		f[i & 1][j] = f[i - 1 & 1][j];
	for (int j = v[i]; j <= m; ++ j)
		f[i & 1][j] = max(f[i & 1][j], f[i - 1 & 1][j - v[i]] + w[i]);		
}

int ans = 0;
for (int j = 1; j <= m; ++ j)
	ans = max(ans, f[n & 1][j]);

由于滚动数组的时候每次仅需拷贝无需清空,所以我们发现可以直接去掉第一维,在枚举到第 i i i 个物品的时候
f [ j ] f[j] f[j] 表示背包中放入总体积为 j j j 的物品的最大价值和。但是由于 0/1 背包每个物品仅能使用一次,所以我们需要
倒序枚举,防止 f [ i ] [ j ] f[i][j] f[i][j] f [ i ] [ j − v [ i ] ] f[i][j - v[i]] f[i][jv[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]);
int ans = 0;
for (int j = 1; j <= m; ++ j) 
	ans = max(ans, f[j]);

Problem A. 数字组合

AcWing 278

给定 N N N 个正整数,从中选出若干个数,使得它们的和是 M M M ,求有多少种选择方案。

1 ≤ N ≤ 100 , 1 ≤ M ≤ 1000 1\\le N \\le 100, 1\\le M \\le 1000 1N100,1M1000

Solution

显然是一个 0/1 背包的模型求总方案数,我们将转移时的 max ⁡ \\max max 改为求和即可。

f [ i , j ] f[i, j] f[i,j] 表示取前 i i i 个数字,和为 j j j 的方案数。这里相当于必须装满的背包。

Code

#include <bits/stdc++.h> 
using namespace std; 
const int N = 1e5 + 6; 
int n, m, s, t;
int a[N];
int f[N];

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++ i)
        scanf("%d", &a[i]);

    f[0] = 1;
    for (int i = 1; i <= n; ++ i) 
        for (int j = m; j >= a[i]; -- j)
            f[j] += f[j - a[i]];
    cout << f[m] << endl;
    return 0;
}

Problem B. 背包问题求具体方案

AcWing 12

N N N 件物品和一个容量是 V V V 的背包。每件物品只能使用一次。

i i i 件物品的体积是 v i v_i vi,价值是 w i w_i wi

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

输出 字典序最小的方案。这里的字典序是指:所选物品的编号所构成的序列。物品的编号范围是 1 … N 1…N 1N

0 < N , V ≤ 1000 , 0 < v i , w i ≤ 1000 0<N,V≤1000,0<v_i,w_i≤1000 0<N,V1000,0<vi,wi1000
Solution

0/1 背包转移之后,倒序再遍历一遍输出方案即可。

因为要求按字典序输出,倒序循环枚举物品,正序循环输出方案即可。

Code

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>

using namespace std;

const int N = 5007;
int n, m;
int f[N][N];
int v[N], w[N];
int main(){
    scanf("%d%d", &n, &m);
    
    for(int i = 1; i <= n; ++ i)
        scanf("%d%d", &v[i], &w[i]);
    for(int i = n; i >= 1; -- i){
        for(int j = m; j >= 0; -- 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]){
            printf("%d ", i);
            j -= v[i];
        }
        
    }
    puts("");
    return 0;
}

Problem C. jury Compromise(多维度 0/1 背包,输出方案)

AcWing 280

N N N 个人中选择 M M M 个人。第 i i i个人有 a i , b i a_i,b_i a0x51.动态规划 - 线性DP(习题详解 × 10)

0x53. 动态规划 - 区间DP(习题详解 × 8)

0x56. 动态规划 - 状态压缩DP(习题详解 × 7)

动态规划之完全背包详解

动态规划01背包问题(例子详解)

动态规划之01背包详解解题报告