0-1背包问题,完全背包问题

0-1背包问题

N个物品(编号1,2,…,N),
重量 int w[N+1]
价值 int v[N+1]
背包容量 int S

  1. 具有重叠子问题
  2. 具有最优子结构

枚举(+剪枝)

每个物品选和不选,形成二叉树,再DFS

动态规划

递归写法
dp(i,x)表示从1~i号物品中选,背包剩余容量为x,此状态下能达到的最大价值。

  • 递推关系
    dp(i,x) = max{ dp(i-1,x), dp(i-1,x-w[i])+v[i] }
  • 递归边界
    i=0或x=0时,dp=0
    x<0时,dp=-∞,表示不可能出现这种情况,在max中淘汰
#include <climits>
#include <algorithm>

const int NINF = INT_MIN;

int DP(int i, int x){
    
    
    if(i==0 || x==0) return 0;
    if(x<0) return NINF;
    return std::max(DP(i-1,x), DP(i-1, x-w[i])+v[i]);
}

int main(){
    
    
    int ans = DP(N, S);
}

递归中存在子问题的重复计算。实际计算量和枚举+剪枝相同,只不过递归是带着“剩余容量”从后往前枚举,后者是带着“已累计重量”从前往后枚举。

递推写法

  • 状态
    dp[i][x]表示从1~i号物品中选,背包剩余容量为x,此状态下能达到的最大价值。
  • 状态转移方程
    d p [ i ] [ x ] = { m a x { d p [ i − 1 ] [ x ] , d p [ i − 1 ] [ x − w [ i ] ] + v [ i ] } if  x ≥ w [ i ] d p [ i − 1 ] [ x ] if  x < w [ i ] dp[i][x]=\begin{cases} max\lbrace dp[i-1][x], dp[i-1][x-w[i]]+v[i] \rbrace &\text{if } x\geq w[i] \\ dp[i-1][x] &\text{if } x<w[i] \end{cases} dp[i][x]={ max{ dp[i1][x],dp[i1][xw[i]]+v[i]}dp[i1][x]if xw[i]if x<w[i]
  • 边界
    dp[0][x]=0, (0<=x<=S)
    dp[i][0]=0,(1<=i<=N)
  • 处理顺序
i \ x 0 1 2 3 S
0 0 0 0 0 0
1 0
2 0
3 0
N 0

状态转移方程中涉及的状态都在(i,x)点的左上方,或上方,因此处理顺序为从左到右,从上到下。

#include <algorithm>

int dp[N+1][S+1]={
    
    }; //边界

for(int i=1; i<=N; i++){
    
    
    for(int x=1; x<=S; x++){
    
    
        if(x>=w[i]) dp[i][x] = std::max(dp[i-1][x], dp[i-1][x-w[i]]+v[i]);
        else dp[i][x] = dp[i-1][x];
    }
}

int ans = dp[N][S];

最终答案点(N,S)在最右下角,可能涉及的点在其左上方、上方形成路径,但在计算之前无法知道是怎样的路径,因此可能计算了大量的对最终答案无用的点。
动态规划依然无法避免对所有情况的枚举,其效率提升的本质是通过“存储状态+状态的无后效性+状态递推公式”加快了状态的计算,省去了重复计算。

  1. 时间复杂度上的优化:O(NS)是极限,顶多省去(N,0)~(N,S-1)
  2. 空间复杂度上的优化:
    在这里插入图片描述
    在计算到中间某一点时,其可能用到的点只有蓝框框住的部分,因此可以把二维数组缩小为一维数组,以x从大到小,i从小到大的顺序更新数组,直到点(N,S)。
int dp[S+1]={
    
    };

for(int i=1; i<=N; i++){
    
    
	for(int x=S; x>=1; x--){
    
    
		if(x>=w[i]) dp[x] = std::max(dp[x], dp[x-w[i]]+v[i]);
		//else dp[x]=dp[x]
	}
}

//进一步优化写法
for(int i=1; i<=N; i++){
    
    
	for(int x=S; x>=w[i]; x--){
    
    
		dp[x] = std::max(dp[x], dp[x-w[i]]+v[i]);
	}
}

完全背包问题

把每件物品的数量修改为无穷多个。

动态规划

递推写法

  • 状态
    同上
  • 状态转移方程
    d p [ i ] [ x ] = { m a x { d p [ i − 1 ] [ x ] , d p [ i ] [ x − w [ i ] ] + v [ i ] } if  x ≥ w [ i ] d p [ i − 1 ] [ x ] if  x < w [ i ] dp[i][x]=\begin{cases} max\lbrace dp[i-1][x], dp[i][x-w[i]]+v[i] \rbrace &\text{if } x\geq w[i] \\ dp[i-1][x] &\text{if } x<w[i] \end{cases} dp[i][x]={ max{ dp[i1][x],dp[i][xw[i]]+v[i]}dp[i1][x]if xw[i]if x<w[i]
  • 边界
    同上
  • 处理顺序
    新的状态转移方程涉及的点在当前点的左方、上方。因此处理顺序不变。

空间优化后,以i与x都从小到大的顺序处理,

int dp[S+1]={
    
    };

for(int i=1; i<=N; i++){
    
    
	for(int x=1; x<=S; x++){
    
    
		if(x>=w[i]) dp[x] = std::max(dp[x], dp[x-w[i]]+v[i]);
		//else dp[x]=dp[x]
	}
}

//进一步优化写法
for(int i=1; i<=N; i++){
    
    
	for(int x=w[i]; x<=S; x++){
    
    
		dp[x] = std::max(dp[x], dp[x-w[i]]+v[i]);
	}
}

猜你喜欢

转载自blog.csdn.net/sinat_37517996/article/details/104615289
今日推荐