dp-背包问题

背包问题理论模板

一个背包总容量为V, 现在有N个物品, 第i个物品容量为weight[i], 价值为value[i], 现在往背包里面装东西, 怎样装才能使背包内物品总价值最大.主要分为3类:

  • 01背包, 每个物品只能取0个,或者1个.
  • 完全背包, 每个物品可以取无限次.
  • 多重背包, 每种物品都有个数限制, 第i个物品最多可以为num[i]个.

总体的,又分为背包刚好装满,与背包没有装满两种情况

01背包问题

每种物品都只有1个,只有选择与不选择两种状态
有N件物品和一个容量为V的背包,每种物品只有一件,可以选择放或不放。第i件物品的重量是w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大。

对于任何只存在两种状态的问题都可以转化为01背包问题

定义状态dp[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。
状态转移方程:
dp[i][v]=max(dp[i-1][v], dp[i-1][v-w[i]]+v[i])

`dp[v]=max(dp[v],dp[v-w[i]]+v[i])`

空间复杂度O(NW)或者O(W),时间复杂度为O(nW)

for(int i=0;i<n;i++){
    for(int j=背包大小;j>=w[i];j--){
        dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
    }
}

分析:
首先,大循环从0到n是指n个物体
其次,内循环是从背包大小到当前物体的重量,是为了减少时间复杂度而这么弄的
对于w[i]是物体的重量,如果剩余背包的大小小于w[i]了,就无需再循环,所以一个端点在w[i]
而j的含义是当前的背包总容量,而容量不可能大于背包大小,所以另一个大小就在背包大小
如果是升序,那么就会对dp[j-w[i]]进行操作,也就意味着,如果j是w[i]的倍数,那么v[i]就会不断地加进去,也就是完全背包问题了
而对于降序的话,也就保证了每次第j个物品都只能被放入1此,而不是多次,就是01背包了

多重背包问题

每种物品最多有n件可用
有N种物品和一个容量为V的背包。第i种物品最多有n件可用,每件体积是c,价值是w。求解将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大。

对于多重背包问题,可以转化为01背包问题,比如2个价值为5,重量为2的物品,可以转化为a和b两个物品,一个价值为5,重量为2,一个价值也为5,重量也为2

状态转移方程:
dp[i][j]=max(dp[i-1][j],dp[i-1][j-k*w[i]]+k*v[i]) 其中0<=k<=c[i]

`dp[j]=max(dp[j],dp[j-k*w[i]]+k*v[i])`

k是每种物品放的数量

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

完全背包问题

每种物品都有无限件
有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的体积是c,价值是w。将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大。

状态转移方程:
dp[i][j]=max(dp[i][j],dp[i-1][v-k*w[i]]+k*v[i]) 其中0<=k*w[i]<=背包大小

`dp[j] = max(dp[j], dp[j - w[i]] + v[i]);`

分析见01背包物体,和01背包一样,就是内循环的循环方向不同而已,一个升,一个降

空间杂度为O(NW)或O(W),时间复杂度为O(NW)

代码

for (int i = 1; i <= n; i++) {
    for (int j = w[i]; j <= 背包大小; j++) {
        dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
    }
}

背包混用

将3种背包进行混用,如果对于有些物品最多只能选一次,有些可以无限选
则利用01背包和完全背包的一行代码不同,进行判断

for(int i=0;i<n;i++){
    if(第i个物品是01背包){
        for(int j=背包大小;j>=w[i];j--){
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
        }
    }else if(第i个物品是完全背包){
        for (int j = w[i]; j <= 背包大小; j++) {
            dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
        }
    }
}

如果再加上多重背包的话
背包混用伪代码

for i= 1 to n
    if 第i件物品属于01背包
        ZeroOnePack(dp,wi,vi)
    else if 第i件物品属于完全背包
        CompletePack(dp,wi,vi)
    else if 第i件物品属于多重背包
        MultiplePack(dp,wi,vi,ni)

总结模板

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
const int N=1000000;
int v[N],w[N];
int dp[N];
void ZeroOnePack(int i){
    for(int j=背包大小;j>=w[i];j--){
        dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
    }
}
void CompletePack(int i){
    for(int j=w[i];j<=背包大小;j++){
        dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
    }
}
void MultiplePack(int i){
    for(int j=m;j>=0;j--)
        for(int k=1;k<=c[i];k++){
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
        }
    }
}
int main(){
    memste(dp,0,sizeof(dp));
    for(int i=0;i<n;i++){
    if(第i个物品是01背包问题){
        ZeroOnePack(i);
    }else if(第i个物品是完全背包问题){
        CompletePack(i);
    }else if(第i个物品是多重背包问题){
        MultiplePack(i);
    }
}
posted @ 2019-08-11 08:12  Emcikem  阅读(288)  评论(0编辑  收藏  举报