算法学习——九种背包问题汇总(超全)

写在前面

本人是垃圾211本科生,算法功底很弱,本文只是为了记录学习笔记而写,玻璃心轻点喷

下文代码均能通过平台测试,但绝对不是最优(仅供参考)

目录

01背包问题

问题描述

问题分析

相关例题

 完全背包问题

问题描述

问题分析

相关例题

多重背包问题

问题描述

问题分析

二进制优化方法

单调队列优化方法

相关例题

 混合背包问题

问题描述

问题分析

相关例题

二维费用背包问题

问题描述

问题分析

相关例题

分组背包问题

问题描述

问题分析

相关例题

有依赖的背包问题

问题描述

问题分析

相关例题

背包问题求方案数

问题描述

问题分析

相关例题

背包问题求具体方案

问题描述

问题分析

相关例题


01背包问题

问题描述

问题分析

由于每种物品只有 不拿 两种状态,故称为01背包

①定义 DP 状态:

f_{i,j} 表示在 1 ~ i 个物品中任取,背包容量为 j 的时候的最大价值

②判断 DP 转移:

想要求得 f_{i,j}

如果新加入的物品 i 不放入背包,则 f_{i,j} = f_{i-1 ,j}

如果新加入的物品 i 放入背包,则 f_{i,j} = f_{i-1,j-weights\left [ i \right ]} + values\left[ i \right ]

状态转移方程为:f_{i,j}=max\left ( f_{i-1,j},f_{i-1,j-weights\left [ i \right ]} +values\left [ i \right ]\right )

在初学时脑海中出现过一个问题,背包问题的最优解不一定是刚刚好放满背包的,为什么f_{i-1,j-weights\left [ i \right ]} 部分的重量是 j-weights\left [ i \right ],万一 f_{i-1,j-weights\left [ i \right ]} 的最优解没有装满背包,岂不是浪费了一部分容量吗

解释:假设对于 1 ~ i - 1 的物品的最优解的总重量为 w,那么既然  f_{i-1,j-weights\left [ i \right ]}  的最优解没有装满背包,也就是说在 f_{i-1,w} 的基础上,增加了一部分背包容积到 j-weights\left [ i \right ]是没有用处的,也就是说 f_{i-1,j-weights\left [ i \right ]} = f_{i-1,w}

③初始化 DP 数组:

初始全为0即可,代表最大价值为0(没有价值为负的物品)

④代码优化

将二维数组(行为物品,列为背包容量)通过滚动的形式变成一维数组

简单解释:二维数组的更新是一行一行更新的,每次更新新的元素取决于该元素的 上一层同列元素 以及 上一层前面列的元素。压缩成一维数组并且 从后向前遍历列 时,上一层同列元素等价于当前元素已有值,上一层前列元素等价于同一层前列元素(因为从后向前遍历时,还未更新到前面的元素,其位置仍未上一层的值)

相关例题

例题链接:01背包问题

#include <iostream>
#include <vector>
using namespace std;

int f[1001];
int main()
{
    int N, V;
    cin >> N >> V;
    vector<int> weights(N + 1, 0);
    vector<int> values(N + 1, 0);
    for (int i = 1; i <= N; i++)
    {
        cin >> weights[i] >> values[i];
    }

    for (int i = 1; i <= N; i++)
    {
        for (int j = V; j >= weights[i]; j--)
        {
            f[j] = max(f[j], f[j - weights[i]] + values[i]);
        }
    }
    cout << f[V] << endl;
    return 0;
}

 完全背包问题

问题描述

问题分析

由于每种物品可以选无限次,故称为完全背包

①定义 DP 状态:

f_{i,j} 表示在 1 ~ i 个物品中任取,背包容量为 j 的时候的最大价值

②判断 DP 转移:

采用思考角度二:想要求得 f_{i,j}

如果新加入的物品 i 不放入背包,则 f_{i,j} = f_{i-1 ,j}

如果新加入的物品 i 放入背包,则需要考虑放多少个,假设我们放入两个物品 i,则状态转移方程 f_{i,j} = f_{i,j-2weights\left [ i \right ]} + 2values\left[ i \right ] 

和两个状态转移方程 

f_{i,j-weights\left [ i \right ]} = f_{i,j-2weights\left [ i \right ]} + values\left[ i \right ] 

 f_{i,j} = f_{i,j-weights\left [ i \right ]} + values\left[ i \right ]

的叠加是一致的。也就是说,f_{i,j-weights\left [ i \right ]} 已由 f_{i,j-2weights\left [ i \right ]} 更新过,不需要再去枚举此处放入多少个物品 i 

状态转移方程为:f_{i,j}=max\left ( f_{i-1,j},f_{i,j-weights\left [ i \right ]} +values\left [ i \right ]\right )

注意01背包和完全背包中状态转移方程的区别

01背包:f_{i,j}=max\left ( f_{i-1,j},f_{i-1,j-weights\left [ i \right ]} +values\left [ i \right ]\right )

完全背包:f_{i,j}=max\left ( f_{i-1,j},f_{i,j-weights\left [ i \right ]} +values\left [ i \right ]\right )

区别在于一个是 f_{i-1,j-weights\left [ i \right ]},一个是 f_{i,j-weights\left [ i \right ]}

解释:因为 f_{i,j-weights\left [ i \right ]} 的最优解中有可能已经放入了物品 i,而01背包每个物品只能放入一次,故状态转移方程中必须使用 f_{i-1,j-weights\left [ i \right ]}

③初始化 DP 数组:

初始全为0即可,代表最大价值为0(没有价值为负的物品)

④代码优化

注意下面解释和01背包的相似性

将二维数组(行为物品,列为背包容量)通过滚动的形式变成一维数组

简单解释:二维数组的更新是一行一行更新的,每次更新新的元素取决于该元素的 上一层同列元素 以及 同一层前面列的元素。压缩成一维数组并且 从前向后遍历列 时,上一层同列元素等价于当前元素已有值,同一层前列元素等价于同一层前列元素(因为从前向后遍历时,已经更新到前面的元素,其位置已经是同一层元素的值)

相关例题

例题链接:完全背包问题

#include <iostream>
#include <vector>
using namespace std;

int f[1001];
int main()
{
    int N, V;
    cin >> N >> V;
    vector<int> weights(N + 1, 0);
    vector<int> values(N + 1, 0);
    for (int i = 1; i <= N; i++)
    {
        cin >> weights[i] >> values[i];
    }

    for (int i = 1; i <= N; i++)
    {
        for (int j = weights[i]; j <= V; j++)
        {
            f[j] = max(f[j], f[j - weights[i]] + values[i]);
        }
    }
    cout << f[V] << endl;
    return 0;
}

多重背包问题

问题描述

问题分析

 由于每种物品可以选特定次,故称为多重背包

本题主要聚焦于 二进制 单调队列 优化方法

多重背包完全可以转化成01背包来处理,将每种物品选若干次等价于有若干个价值一样的物品,每种物品只能选一次

二进制优化方法

现在考虑这样一种情况,假设我有3个价值为1的物品 A_{1},A_{2},A_{3},最优解会选择2个价值为1的物品。那么此时,我选取 A_{1},A_{2} 和选取A_{1},A_{3} 是完全一致的。

而直接使用1背包求解会重复计算上述两种情况(实际可能会更多)

所以一种想法是:采用二进制分组使拆分方法不会出现重复计算的情况

举例子说明:

  • 6 = 1 + 2 + 3(不足4)
  • 8 = 1 + 2 + 4 + 1(不足8)
  • 18 = 1 + 2 + 4 + 8 + 3(不足16)
  • 31 = 1 + 2 + 4 + 8 + 16

因为任何一个十进制数 num 都可以由若干个不重复的 2^{i} \ i\epsilon \left [ 0,\left \lfloor \log_{2}num \right \rfloor \right ] 求和得到,证明也很简单,因为每个十进制数都可以转化成二进制数,而二进制数字显然可以由若干个不重复的 2^{i} 求和得到

举例子说明:(1,2)表示 重量为1 价值为2 的物品

  • 有33个(1,2) => 33 = 1 + 2 + 4 + 8 + 16 + 2(不足32)
  • 等效于背包中有(1,2),(2,4),(4,8),(8,16),(16,32),(2,4)六件物品(33件 -> 6件)
    • 如果最优解要选13个,那么13 = 1 + 4 + 8
    • 如果最优解要选23个,那么23 = 1 + 2 + 4 + 16
    • 如果最优解要选16个,那么16 = 16

可以看到,无论最优解最终选择多少个物品,都可以由这些二进制分组组成

单调队列优化方法(未完善)

单调队列类似优先队列,但优先队列本质为维护一个最大/最小堆,而单调队列的维护方法是自己定义的,并且单调队列插入元素的过程中有可能会弹出元素

一般实现如下:

(1)push(num),如果入口元素小于 num 则 弹出(直到队列为空或者出口元素大于等于num),完成后插入num

(2)pop(num),如果出口元素等于 num 则弹出,否则不做操作

(3)getMax(),出口元素即为队列中的最大值

不详细解释原理(下文给出了一种实现),相关题目链接:滑动窗口的最大值

class MonotonicQueue
{
public:
    // deque 为C++STL库中双向队列
    deque<int> nums;
    void push(int num)
    {
        while ((int)nums.size() && nums.back() < num)
        {
            nums.pop_back();
        }
        nums.push_back(num);
    }
    void pop(int num)
    {
        if (nums.size() == 0)
            return;
        if (nums.front() == num)
            nums.pop_front();
    }
    int getMax()
    {
        return nums.front();
    }
};

首先,我们用最朴素的方法来实现多重背包

当决定物品 i 放几个的时候,枚举放0个到放最大个(背包放得下 + 个数足够)

故 f_{i,j} 为下列各个式子中的最大值:

放1个物品 i:f_{i,j-1weights\left [ i \right ]} + 1values\left [ i \right ]

放2个物品 i:f_{i,j-2weights\left [ i \right ]} + 2values\left [ i \right ] 

放3个物品 i:f_{i,j-3weights\left [ i \right ]} + 3values\left [ i \right ] 

……………………

放k个物品 i:f_{i,j-kweights\left [ i \right ]} + kvalues\left [ i \right ]

k为满足  j \geq kweights[i] \ and \ k \leq nums[i]  的最大值

 观察公式,不难发现下列式子 模 weights[i] 是同余的

j

j-1weights[i]

 j-2weights[i]

j-3weights[i]

……………………

 j-kweights[i]

我们不妨假设 j = sweights[i] + t

此时,我们将上面的状态转移式子写到头(当成完全背包来看):

放1个物品 i:f_{i,j-1weights\left [ i \right ]} + 1values\left [ i \right ]

放2个物品 i:f_{i,j-2weights\left [ i \right ]} + 2values\left [ i \right ] 

放3个物品 i:f_{i,j-3weights\left [ i \right ]} + 3values\left [ i \right ] 

……………………

放k个物品 i:f_{i,j-kweights\left [ i \right ]} + kvalues\left [ i \right ]

……………………

放s个物品 i:f_{i,t} + s values\left [ i \right ]

相关例题

例题链接:无优化_多重背包问题 I

#include <iostream>
#include <vector>
using namespace std;

int f[10001];
int main()
{
    int N, V;
    cin >> N >> V;
    vector<int> weights;
    vector<int> values;
    for (int i = 0; i < N; i++)
    {
        int weight, value, num;
        cin >> weight >> value >> num;
        for (int j = 1; j <= num; j++)
        {
            weights.push_back(weight);
            values.push_back(value);
        }
    }

    for (int i = 0; i < weights.size(); i++)
    {
        for (int j = V; j >= weights[i]; j--)
        {
            f[j] = max(f[j], f[j - weights[i]] + values[i]);
        }
    }
    cout << f[V] << endl;
    return 0;
}

 例题链接:二进制优化_多重背包问题 II

#include <iostream>
#include <vector>
using namespace std;

int f[10001];
int main()
{
    int N, V;
    cin >> N >> V;
    vector<int> weights;
    vector<int> values;
    for (int i = 0; i < N; i++)
    {
        int weight, value, num;
        cin >> weight >> value >> num;
        int k = 1;
        while (num >= k)
        {
            weights.push_back(k * weight);
            values.push_back(k * value);
            num -= k;
            k *= 2;
        }
        if (num > 0)
        {
            weights.push_back(num * weight);
            values.push_back(num * value);
        }
    }

    for (int i = 0; i < weights.size(); i++)
    {
        for (int j = V; j >= weights[i]; j--)
        {
            f[j] = max(f[j], f[j - weights[i]] + values[i]);
        }
    }
    cout << f[V] << endl;
    return 0;
}

 混合背包问题

问题描述

问题分析

由于有的物品能取一次,有的物品能取k次,有个物品能无限取,故称作混合背包

将问题简单化成多重背包

物品即便有无限次,最多也只能取到 背包容量 / 物品重量 的个数(向下取整)

相关例题

例题链接:混合背包问题

#include <iostream>
#include <vector>
using namespace std;

int f[10001];
int main()
{
    int N, V;
    cin >> N >> V;
    vector<int> weights;
    vector<int> values;
    for (int i = 0; i < N; i++)
    {
        int weight, value, num;
        cin >> weight >> value >> num;
        if (num == 0)
            num = V / weight;
        if (num == -1)
            num = 1;
        int k = 1;
        while (num >= k)
        {
            weights.push_back(k * weight);
            values.push_back(k * value);
            num -= k;
            k *= 2;
        }
        if (num > 0)
        {
            weights.push_back(num * weight);
            values.push_back(num * value);
        }
    }

    for (int i = 0; i < weights.size(); i++)
    {
        for (int j = V; j >= weights[i]; j--)
        {
            f[j] = max(f[j], f[j - weights[i]] + values[i]);
        }
    }
    cout << f[V] << endl;
    return 0;
}

二维费用背包问题

问题描述

问题分析

不同于01背包,此时的背包需要同时考虑两个指标,所以需要在状态中增加一维存放第二种价值

相关例题

例题链接:二维费用背包问题

#include <iostream>
#include <vector>
using namespace std;

int f[101][101];
int main()
{
    int N, V, M;
    cin >> N >> V >> M;
    vector<int> weights(N + 1, 0);
    vector<int> values(N + 1, 0);
    vector<int> volumns(N + 1, 0);

    for (int i = 1; i <= N; i++)
    {
        cin >> volumns[i] >> weights[i] >> values[i];
    }

    for (int i = 1; i <= N; i++)
    {
        for (int j = V; j >= volumns[i]; j--)
        {
            for (int k = M; k >= weights[i]; k--)
            {
                f[j][k] = max(f[j][k], f[j - volumns[i]][k - weights[i]] + values[i]);
            }
        }
    }
    cout << f[V][M] << endl;
    return 0;
}

分组背包问题

问题描述

问题分析

以下分析均本人胡思乱想,可能会有错误的地方

每一组中的物品只能选一个,我们 用一个物品来代替一组物品,该物品有 不同的表现形式 可以选择。举个例子,例如一组物品 [(1,2), (1,3), (4,4), (9,10)],将其等价于一个物品,该物品的重量和价值可以在四种之间选择

那么解题思路和01背包完全一致,不同之处在于最内层循环中,额外加入一层循环遍历该物品的若干种表现形式求最大值

相关例题

例题链接:分组背包问题

#include <iostream>
#include <vector>
using namespace std;

int f[101];
int main()
{
    int N, V, S;
    cin >> N >> V;
    vector<vector<int>> weights(N + 1, vector<int>{0});
    vector<vector<int>> values(N + 1, vector<int>{0});
    for (int i = 1; i <= N; i++)
    {
        cin >> S;
        for (int j = 0; j < S; j++)
        {
            int weight, value;
            cin >> weight >> value;
            weights[i].push_back(weight);
            values[i].push_back(value);
        }
    }

    for (int i = 1; i <= N; i++)
    {
        for (int j = V; j >= 0; j--)
        {
            for (int k = 1; k < weights[i].size(); k++)
            {
                if (j >= weights[i][k])
                    f[j] = max(f[j], f[j - weights[i][k]] + values[i][k]);
            }
        }
    }
    cout << f[V] << endl;
    return 0;
}

有依赖的背包问题

问题描述

问题分析

本题需要分组背包和树形DP的知识

分组背包请看上文,树形DP请看 树形DP教程

选择一个物品的前提是选择该物品的父物品,故称为有依赖的背包问题

该题为典型的背包类树形DP(另一道典型题目:选课

树形DP的核心就是当分析某个节点的时候,不考虑它的父节点,而是聚焦于当前节点的子树

①定义 DP 状态:

f_{i,j} 表示在考虑 i 节点的子树时,i 节点物品必须选择 并且分给该子树的背包容量为 j 时的最大价值

②判断 DP 转移:

首先,由于 i 节点物品必须选择,实际分给子节点的容量为 j - weights[i]

那么 f_{i,j} 就是 排列组合枚举子节点们分得的容量 中的最大值

例如,我们以2节点的子树为例(假设2节点的重量为3,4节点的重量为1,5节点的重量为2)

假设我们想要求得 f[2,7],容易想到的解决方法为:将7的容量分出3给父节点2,剩余的4分给两个子节点4和5(枚举 0+4,1+3,3+1,4+0 这些组合,找到一个最大值)

f[2,7] = max\left ( f[4,0] +f[5,4],f[4,1]+f[5,3],f[4,2]+f[5,2],f[4,3]+f[5,1],f[4,4]+f[5,0] \right )

但是在子节点个数不确定时,枚举出若干排列组合只能用回溯算法求解,这时的复杂度一定超出了可行范围。于是,我们采用 分组背包的思想 优化问题

我们把4、5两个子节点看作两组物品,每组物品有5个,(weight, value):

第一组:(0,f[4,0]),(1,f[4,1]),(2,f[4,2]),(3,f[4,3]),(4,f[4,4])

第二组:(0,f[5,0]),(1,f[5,1]),(2,f[5,2]),(3,f[5,3]),(4,f[5,4])

问题转化为:每一组物品只能选择一个,背包总容量为4时的最大价值

上文的 f[4,0] +f[5,4] 等价于第一组物品选择第1个,第二组物品选择第5个

上文的 f[4,2] +f[5,2] 等价于第一组物品选择第3个,第二组物品选择第3个

由于 f[i,j] \leq f[i,k] ~~~~ if ~~ j \leq k,所以分组背包的最优解一定是:

max\left ( f[4,0] +f[5,4],f[4,1]+f[5,3],f[4,2]+f[5,2],f[4,3]+f[5,1],f[4,4]+f[5,0] \right )

上述式子中五个组合的其中一个

这时问题就变成了纯粹的分组背包问题,对于求解 f(i,j) 来说,分组背包的组数为 \left | Son(i)) \right |,背包容量为 j-weighs[i],每组的物品数为 j-weights[i]+1,每组第 i 个物品:

weight_{i} = i-1 

value_{i} = f[son,i-1]

③实现细节:

在代码实现上,做了一点小小的优化,还是以刚才的例子说明:

问题 f[2,7] 对应分给子树的容量为 7 - weights[2],在写代码的时候我们假设分给子树的容量就是7,计算后 先重新选择物品2修改一部分数据,后将非法的数据归零,即:

选择父节点物品,倒序 修改数据:

f[2,7] = f[2,7-weights[2]] + values[2]

f[2,6] = f[2,6-weights[2]] + values[2]

……………………

f[2,weights[2]] = f[2,0] + values[2]

非法数据:

f[2,0] = f[2,1] = ... = f[2,weighs[2]-1] = 0

这样处理使得循环的边界条件得到了简化,具体请看下文的代码实现

相关例题

例题链接:有依赖的背包问题

#include <iostream>
#include <vector>
using namespace std;

class Node
{
public:
    int weight;
    int value;
    vector<int> sons;
};

// 第 i 个节点选,并且给子树分配 j 个容量的最大价值
int f[101][101];
vector<Node> nodes(101, Node());
int N, V;

void dfs(int index)
{
    // 分组背包外层循环:遍历分组
    for (int i = 0; i < nodes[index].sons.size(); i++)
    {
        // 通过递归求解子树问题
        int son = nodes[index].sons[i];
        dfs(son);

        // 分组背包中层循环:遍历背包容量
        for (int j = V; j >= 0; j--)
        {
            // 分组背包内层循环:遍历组内物品
            for (int k = 0; k <= j; k++)
            {
                // 第 k 个物品对应的重量和价值(k从0开始)
                int weight = k;
                int value = f[son][k];
                f[index][j] = max(f[index][j], f[index][j - weight] + value);
            }
        }
    }

    // 必须选择父节点
    for (int j = V; j >= nodes[index].weight; j--)
        f[index][j] = nodes[index].value + f[index][j - nodes[index].weight];

    // 处理非法节点
    for (int j = 0; j < nodes[index].weight; j++)
        f[index][j] = 0;
}

int main()
{
    cin >> N >> V;
    int root = -1;
    nodes.resize(N + 1);
    for (int i = 1; i <= N; i++)
    {
        int num;
        cin >> nodes[i].weight >> nodes[i].value >> num;
        if (num != -1)
            nodes[num].sons.push_back(i);
        root = num == -1 ? i : root;
    }
    dfs(root);
    cout << f[root][V] << endl;
    return 0;
}

背包问题求方案数

问题描述

问题分析

该题目求的是最优选的方案数,而不是最大价值,但解题思路大同小异

①定义 DP 状态:

首先思考,为什么会有多种最优选法。重新回顾01背包的状态转移方程:

f_{i,j}=max\left ( f_{i-1,j},f_{i-1,j-weights\left [ i \right ]} +values\left [ i \right ]\right )

不难发现,当出现 f_{i-1,j}=f_{i-1,j-weights\left [ i \right ]} +values\left [ i \right ] 的情况时,就有了两条选择物品的分支。所以我们的解题思路是:开两个数组 f 和 nums,其中 f[i][j] 表示选前 i 个物品,背包容量为 j 时的最大价值,nums[i][j] 表示选前 i 个物品,背包容量为 j 时的最优方案数

② 判断 DP 转移:

(1)f_{i-1,j}=f_{i-1,j-weights\left [ i \right ]} +values\left [ i \right ]

第 i 个物品既可以选也可以不选,所以方案数为二者之和:

 nums[i][j] = nums[i-1][j] + nums[i-1][j-weigts[i]]

(2)f_{i-1,j}> f_{i-1,j-weights\left [ i \right ]} +values\left [ i \right ]

第 i 个物品必须不选,所以方案数为:

nums[i][j] = nums[i-1][j]

(3)f_{i-1,j}< f_{i-1,j-weights\left [ i \right ]} +values\left [ i \right ]

第 i 个物品必须要选,所以方案数为:

nums[i][j] = nums[i-1][j-weigts[i]]

③ 初始化 DP 数组:

对于记录方案数的数组 nums,初值全部设置成1,因为一个物品也不放也是一种方案

相关例题

例题链接:背包问题求方案数

#include <iostream>
#include <vector>
using namespace std;
#define mod 1000000007
int f[1001];
int nums[1001];
int main()
{
    int N, V;
    cin >> N >> V;
    vector<int> weights(N + 1, 0);
    vector<int> values(N + 1, 0);
    for (int i = 1; i <= N; i++)
    {
        cin >> weights[i] >> values[i];
    }
    for (int i = 0; i <= V; i++)
        nums[i] = 1;

    for (int i = 1; i <= N; i++)
    {
        for (int j = V; j >= 0; j--)
        {
            if (j < weights[i])
                continue;

            if (f[j - weights[i]] + values[i] == f[j])
            {
                nums[j] = (nums[j] + nums[j - weights[i]]) % mod;
            }
            else if (f[j - weights[i]] + values[i] > f[j])
            {
                nums[j] = nums[j - weights[i]];
            }
            f[j] = max(f[j], f[j - weights[i]] + values[i]);
        }
    }
    cout << nums[V] << endl;
    return 0;
}

背包问题求具体方案

问题描述

问题分析

题目要求输出 字典序最小的方案,考虑此条件,如果存在一种方案包含物品1,那么必须选择物品1,然后在 2 ~ N 个物品中选择一个最优解。所以我们反转01背包中的 DP 状态

①定义 DP 状态

f_{i,j} 表示在 i ~ N 个物品中任取,背包容量为 j 的时候的最大价值

②判断 DP 转移

想要求得 f_{i,j}

如果新加入的物品 i 不放入背包,则 f_{i,j} = f_{i+1 ,j}

如果新加入的物品 i 放入背包,则 f_{i,j} = f_{i+1,j-weights\left [ i \right ]} + values\left[ i \right ]

状态转移方程为:f_{i,j}=max\left ( f_{i+1,j},f_{i+1,j-weights\left [ i \right ]} +values\left [ i \right ]\right )

可以看到,状态转移方程中除了将 i - 1 变成了 i + 1,其余没有区别,所以代码书写只用反转外层遍历物品的顺序即可,不过需要注意的是本题不能使用滚动数组压缩成一维(如果压缩成一维则无法进行下面的回溯输出方案环节)

③回溯输出方案

首先,最终背包最大价值为 f_{1,N}

(1)如果最优方案必须要选择第一个物品,说明:f_{1,N} = f_{2,N-weights[1]} + values[1]。换句话说,由于最优方案必须选择物品1,那么计算 f_{1,N} 时,一定是由 f_{2,N-weights[1]} + values[1]计算得来

(2)如果最优方案必须不选第一个物品,说明:f_{1,N} = f_{2,N}。换句话说,由于最优方案必须不选择物品1,那么计算 f_{1,N} 时,一定是由 f_{2,N} 计算得来

(3)如果既有选择第一个物品的最优方案,也有不选择第一个物品的最优方案,说明:f_{1,N} = f_{2,N-weights[1]} + values[1] = f_{2,N}。此时根据题意,我们需要求解字典序最小的方案,故 如果出现可选某个物品的情况,必须选择该物品

(4)判断完毕物品1,循环判断物品2:

如果物品1必须选择,则下一次判断 f_{2,N-weights[1]}

如果物品1必须不选,则下一次判断f_{2,N}

相关例题

例题链接:背包问题求具体方案

#include <iostream>
#include <vector>
using namespace std;

// f[i][j]: 第 i ~ N 个物品,背包容量为 j
int f[1005][1005];
int main()
{
    int N, V;
    cin >> N >> V;
    vector<int> weights(N + 1, 0);
    vector<int> values(N + 1, 0);
    for (int i = 1; i <= N; i++)
    {
        cin >> weights[i] >> values[i];
    }

    for (int i = N; i > 0; i--)
    {
        for (int j = 0; j <= V; j++)
        {
            if (j >= weights[i])
                f[i][j] = max(f[i + 1][j], f[i + 1][j - weights[i]] + values[i]);
            else
                f[i][j] = f[i + 1][j];
        }
    }

    vector<int> conditions(N + 1, 0);
    int curN = 1;
    int curV = V;
    while (curN <= N)
    {
        // 必须选 curN 物品
        if (curV >= weights[curN] && f[curN][curV] == f[curN + 1][curV - weights[curN]] + values[curN])
        {
            conditions[curN] = 1;
            curV -= weights[curN];
        }
        // 不选 curN 物品
        else if (f[curN][curV] == f[curN + 1][curV])
        {
            conditions[curN] = 0;
        }
        curN++;
    }
    for (int i = 1; i <= N; i++)
        if (conditions[i])
            cout << i << " ";
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值