动态规划问题中边界和边界值的确定

本文通过01背包问题阐述动态规划的解决思路,包括定义状态、状态转移方程的建立以及边界状态的确定。文章详细介绍了最大价值和最优方案数的状态转移方程,并提供了两种不同空间复杂度的C++实现,分别是O(nV)和O(V)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引言

在动态规划问题中,首先需要抽象出原问题的状态,然后写出状态转移方程,最后根据边界状态值和转移方程求解所有的状态。在本文中,我们将以01背包问题为例来探讨分析如何确定边界状态和边界状态值。

01背包问题的最优方案数(原题链接

题目描述

n n n件物品,每件物品的重量为 w i w_i wi,价值为 c i c_i ci。现在需要选出若干件物品放入一个容量为 V V V的背包中(每件物品至多选一次),使得在选入背包的物品重量之和不超过容量 V V V的前提下,让背包中物品的价值之和最大,求最大价值与对应的最优方案数。

输入描述

第一行两个整数 n n n V V V ( 1 ≤ n ≤ 100 , 1 ≤ V ≤ 1 0 3 ) (1 \le n\le 100, 1 \le V \le 10^3) (1n100,1V103),分别表示物品数量、背包容量;

第二行为用空格隔开的 n n n​个整数 w i w_i wi 1 ≤ w i ≤ 100 1\le w_i \le100 1wi100​),表示物品重量;

第三行为用空格隔开的 n n n​个整数​ c i c_i ci 1 ≤ c i ≤ 100 1\le c_i \le 100 1ci100​),表示物品价值。

输出描述

输出两个整数,分别表示最大价值与最优方案数,中间用空格隔开。由于结果可能很大,因此将结果对
取模后输出。

样例1

输入

3 5
1 2 5
4 2 6

输出

6 2

解释
假设物品编号为从1到3,那么选择第1、2件物品、或者只选择第3件物品时,物品重量不会超过背包容量,对应的价值是 4 + 2 = 6 4+2=6 4+2=6​​,是最大价值。因此共有2种最优方案。

问题分析

这个问题需要求解两个值,分别是最大价值和最优方案数。

最大价值

对于最大价值,令dp[i][v]表示从前i件物品中选择若干件装入容量为v的背包,在不超过背包容量的前提下,所能装入的最大物品价值。其状态转移方程为:
dp[i][v] = { dp[i-1][v] , if v < w[i] or dp[i-1][v] > dp[i-1][v-w[i]] + c[i] dp[i-1][v-w[i]] + c[i] , else \text{dp[i][v]}=\left\{ \begin{aligned} &\text{dp[i-1][v]}, \quad\quad \quad\quad \quad \text{if v < w[i] or dp[i-1][v] > dp[i-1][v-w[i]] + c[i]} \\ &\text{dp[i-1][v-w[i]] + c[i]},\quad \text{else} \\ \end{aligned} \right. dp[i][v]={dp[i-1][v],if v < w[i] or dp[i-1][v] > dp[i-1][v-w[i]] + c[i]dp[i-1][v-w[i]] + c[i],else
接下来确定上述状态转移方程的边界。对于dp数组的第一维,计算dp[1][v]时,需要用到dp[0][v],同时发现无法通过状态转移方程计算dp[0][v],因此dp[0][v] ( 0 ≤ v ≤ V 0 \le v \le V 0vV)就是一组边界。对于dp数组的第二维,计算dp[i][0]时,需要用到dp[i-1][0],通过递推可知最终需要用到dp[0][0]。综上所述,这个状态转移方程的边界为:dp[0][v] ( 0 ≤ v ≤ V 0 \le v \le V 0vV)。

边界的含义是从前0件物品中选若干件装入容量为v的背包中所能获得的最大价值,根据这个意义应该有dp[0][v] = 0。换个角度考虑,整个状态转移方程是从此边界开始的,因此dp[0][v]应该等于dp数组所能取到的最小值,而这个最小值恰好是0。

最优方案数

对于最优方案数,令cnt[i][v]表示从前i件物品中选择若干件装入容量为v的背包中,可以使得背包中物品价值最大的装入方案数。其状态转移方程为:
cnt[i][v] = { cnt[i-1][v] , if v < w[i] or dp[i-1][v] > dp[i-1][v-w[i]] + c[i] cnt[i-1][v-w[i]] , if v ≥ w[i] and dp[i-1][v] > dp[i-1][v-w[i]] + c[i] cnt[i-1][v]+cnt[i-1][v-w[i]] , if v ≥ w[i] and dp[i-1][v] == dp[i-1][v-w[i]] + c[i] \text{cnt[i][v]}=\left\{ \begin{aligned} &\text{cnt[i-1][v]}, \quad\quad\quad \text{if v < w[i] or dp[i-1][v] > dp[i-1][v-w[i]] + c[i]} \\ &\text{cnt[i-1][v-w[i]]},\quad \text{if v$\ge$w[i] and dp[i-1][v] > dp[i-1][v-w[i]] + c[i]} \\ &\text{cnt[i-1][v]+cnt[i-1][v-w[i]]},\quad \text{if v$\ge$w[i] and dp[i-1][v] == dp[i-1][v-w[i]] + c[i]} \end{aligned} \right. cnt[i][v]= cnt[i-1][v],if v < w[i] or dp[i-1][v] > dp[i-1][v-w[i]] + c[i]cnt[i-1][v-w[i]],if vw[i] and dp[i-1][v] > dp[i-1][v-w[i]] + c[i]cnt[i-1][v]+cnt[i-1][v-w[i]],if vw[i] and dp[i-1][v] == dp[i-1][v-w[i]] + c[i]
接下来确定上述状态转移方程的边界。对于cnt数组的第一维,计算cnt[1][v]时,需要用到cnt[0][v],同时发现无法通过状态转移方程计算cnt[0][v],因此cnt[0][v]( 0 ≤ v ≤ V 0\le v\le V 0vV)就是一组边界。对于cnt数组的第二维,计算cnt[i][0]时,需要用到cnt[i-1][0],通过递推可知最终需要用到cnt[0][0]。综上所述,这个状态转移方程的边界为:cnt[0][v]( 0 ≤ v ≤ V 0\le v\le V 0vV)。

dp数组的边界值不同,cnt数组的边界值不太容易通过边界的含义来确定,因为cnt[0][v]的含义是从前0件物品中选若干件装入容量为v的背包中,可以使得背包中物品价值最大的装入方案数。我们从cnt数组的取值范围来考虑,根据题意,最优解是一定存在的,只不过是存在几个最优解的问题。根据状态转移方程,在一定的条件下,有cnt[1][v]=cnt[0][v],显然cnt[1][v]的值至少是1,因此cnt[0][v]的值应该初始化为1。

源代码

在上面的讨论中,用于记录状态的dpcnt都是二维数组,整个算法的空间复杂度为O(nV)。我们可以通过滚动数组的方式,将空间复杂度减小到O(V),下面分别给出空间复杂度为O(nV)和O(V)的实现。

O(nV)空间复杂度

#include<bits/stdc++.h>
using namespace std;
const int maxn = 105;
const int maxV = 1e3 + 5;
int dp[maxn][maxV];
int cnt[maxn][maxV];
int n, V;
int w[maxn], c[maxn];

int main(){
    scanf("%d %d", &n, &V);
    for(int i = 1; i <= n; i++)
        scanf("%d", &w[i]);
    for(int i = 1; i <= n; i++)
        scanf("%d", &c[i]);
    //初始化dp和cnt数组
    for(int v = 0; v <= V; v++){
        dp[0][v] = 0;
        cnt[0][v] = 1;
    }
    for(int i = 1; i <= n; i++){
        for(int v = 0; v <= V; v++){
            dp[i][v] = dp[i - 1][v];
            cnt[i][v] = cnt[i - 1][v];
            if(v >= w[i] && dp[i][v] < dp[i - 1][v - w[i]] + c[i]){
                dp[i][v] = dp[i - 1][v - w[i]] + c[i];
                cnt[i][v] = cnt[i - 1][v - w[i]];
            }else if(v >= w[i] && dp[i][v] == dp[i - 1][v - w[i]] + c[i]){
                cnt[i][v] = (cnt[i][v] + cnt[i - 1][v - w[i]]) % 10007;
            }
        }
    }
    printf("%d %d", dp[n][V], cnt[n][V]);

}

O(V)空间复杂度

#include<bits/stdc++.h>
using namespace std;
const int maxn = 105;
const int maxV = 1e3 + 5;
int dp[maxV];
int cnt[maxV];
int n, V;
int w[maxn], c[maxn];

int main(){
    scanf("%d %d", &n, &V);
    for(int i = 1; i <= n; i++)
        scanf("%d", &w[i]);
    for(int i = 1; i <= n; i++)
        scanf("%d", &c[i]);
    //初始化dp和cnt数组
    for(int v = 0; v <= V; v++){
        dp[v] = 0;
        cnt[v] = 1;
    }
    for(int i = 1; i <= n; i++){
        for(int v = V; v >= 0; v--){
            if(v >= w[i] && dp[v] < dp[v - w[i]] + c[i]){
                dp[v] = dp[v - w[i]] + c[i];
                cnt[v] = cnt[v - w[i]];
            }else if(v >= w[i] && dp[v] == dp[v - w[i]] + c[i]){
                cnt[v] = (cnt[v] + cnt[v - w[i]]) % 10007;
            }
        }
    }
    printf("%d %d", dp[V], cnt[V]);
}

参考

胡凡、曾磊《算法笔记》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值