背包dp

1.01背包

问题描述:

有N件物品和一个容量为v的背包。第i件物品的重量是c[i],价值是w[i]。

求解将哪些物品装入背包可使价值总和最大。

(这类问题用贪心写结果是错误的,可以试试)

基本思路:

 这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。

用子问题定义状态:即f[i][v]表示前i件物品恰好放入一个容量为v的背包可以获得

的最大价值。其状态转移方程便是:

tab[i][j]=max(tab[i-1][j-weight[i]],tab[i-1][j]) ({i,j|0<i<=n,0<=j<=total})

 其中i表示放第i个物品,j表示背包所容纳的重量,那么tab[i-1][j-weight[i]]

+value[i]表示放入第i物品,刚开始接触会有疑问。tab[i-1][j-weight[i]]这个值,

可以这样理解:tab[i-1][j]为装到上一个物品在背包j容量时的最佳值,那么如果我

要求在j容量的时候放入现在的i物品的价值,那么是不是要先得到容量为(j-weight[i])

时候的价值,即先得到tab[i-1][j-weight[i]],所以tab[i-1][j-weight[i]]+value[i]为放入

第i物品的价值;tab[i-1][j]就是不放第i个物品。

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


优化空间复杂度:

 以上方法的时间和空间的复杂度均为O(VN),其中时间复杂度应该已经不能再优化了,

但空间复杂度却可以优化到O(V)。

 先考虑上面讲的基本思路如何实现,肯定是有一个主循环i=1...N,每次算出来二维数组

f[i][0...V]的所有值。那么,如果只用一个数组f[0...V],能不能保证第i次循环结束后发f[v]中

表示的就是我们定义的状态f[i][v]呢? f[i][v]是由f[i-1][v]和f[i-1][v-c[i]]两个子问题递推而来,

能否保证在推f[i][v]时(也即在第i次主循环中推f[v]时)能够能够得到f[i-1][v]和f[i-1][v-c[i]]

的值呢?事实上,这要求在每次主循环中我们以v=V...0的顺序推f[v],这样才能保证推f[v]时,

f[v-c[i]]保存的是状态f[i-1][v-c[i]]的值。如果将v的循环顺序从上面的逆序改成顺序的话,那

么成了f[i][v]由f[i][v-c[i]]推知,与本题意不符,但它却是完全背包最简捷的解决方案,故学习

只用一维数组解01背包问题是十分必要的。

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


二维转一维俗称滚动数组

初始化细节:

 我们看到求最优解的背包问题题目中,是事实有两种不太相同的问法。有的题目要求“恰好

装满背包”,时的最优解,有的题目则并没有要求必须把背包装满。一种区别这两种问法的实现方

法是在初始化的时候有所不同。

 如果是第一种问法,要求恰好装满背包,那么在初始化时除了f[0]为0,其他f[1...V]均设为-

这样就可以保证最终得到的f[N]是一种恰好装满背包的最优解。

 如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将f[0...V]全部设为0。

为什么呢?可以这样理解:初始化的f数组事实上就是在没有任何物品可以放入背包时的合法状态,

如果要求背包恰好装满,那么此时只有容器为0的背包可以被价值为0的nothing“恰好装满”,其他容量

的背包均没有合法的解,属于未定义的状态,他们的值就都是-了。如果背包并非必须被装满,那么任

何容量的背包都有一个合法解“什么都不装”,这个解的价值为0,所以初始化时状态的值也就全部为0了。

这个小技巧完全可以推广到其他类型的背包问题。

2.完全背包

有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将

哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

完全背包按其思路仍然可以用一个二维数组来写出:

f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k*c[i]<=v}

想必大家看出了和01背包的区别,这里的内循环是顺序的,而01背包是逆序的。现在关键的是考虑:为何完全

背包可以这么写?

现在我们回忆下01背包逆序的原因?是为了max中的两项是前一状态,这就对了。

那么这里,我们顺序写,这里的max中的两项当然就是当前状态的值了,为何?因为每种物品都是无限的。

当我们把i从1到N循环时,f[v]表示容量为v在前i种物品时所得的状态,这里我们要添加的不是前一个物品,

而是当前物品。所以我们要考虑的是当前状态。

3.多重背包

有N种物品和一个容量为V的背包,第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i],求解将哪些

物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

这题目和完全背包问题很类似基本的方程只需将完全背包的方程略微一改即可,因为对于第i种物品有n[i]+1

种策略:取0件,取1件.....取n[i]件。令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值,则有状态

转移方程:

f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k<=n[i]}

这里同样转换为01背包,对于普通 的。就是多了一个中间的循环,把j=0~bag[i],表示把第i种背包从取0件枚举到

bag[i]件。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值