背包问题整理

来源:互联网 发布:js点击按钮切换div 编辑:程序博客网 时间:2024/06/16 15:08

准确一点说是对背包九讲的学习笔记 …

思想和方法:

在学习过程中了解的求解DP问题的思想,以及部分细节方法

1.DP初始化为一已知的合法解
2.滚动数组优化
3.”拆分物品”的思想和方法
4.当发现由熟悉的动态规划题目变形得来的题目时,在原来的状态中加一维以满足新的限制是一种比较通用的方法

01背包

问题:
有N件物品和一个容量为V的背包,第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。

思路:
我们设dp[i][j]表示前i件物品恰放入一个容量为j的背包可以获得的最大价值
策略为对每个物品选或不选
当前最优状态由在前i-1个物品的最优状态基础上选或不选当前物品决定

代码:

for(int i=1;i<=n;i++){    for(int j=v;j>=0;j--)    //第二层for顺序倒叙皆可---因为二维存储状态,前i-1个物品的状态都已确定     {        if(j>=c[i])        dp[i][j]=max(dp[i-1][j],dp[i-1][j-c[i]]+w[i]);        else dp[i][j]=dp[i-1][j];//更新完全    }}for(int i=1;i<=n;i++)//附滚动数组优化 {    for(int j=v;j>=c[i];j--)//枚举到c[i]即可,小于c[i]会产生负数下标     dp[j]=max(dp[j],dp[j-c[i]]+w[i]);}

完全背包

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

思路:
我们设dp[i][j]表示前i种物品恰放入一个容量为j的背包可以获得的最大价值
策略为取0件、取1件、取2件…等很多种
则用自身的之前的最优状态在前i-1种物品选取的最优状态基础上选取k个当前物品得到的状态取最优,得到状态转移方程如下:
dp[i][j]=max(dp[i][j],dp[i-1][j-c[i]*k]+w[i]*k); (0<=c[i]*k<=v)
时间复杂度O(n^3)

考虑优化:
首先有一简单有效的优化—若两件物品i,j满足c[i]<=c[j]且w[i]>=w[j],则将物品j去掉,不用考虑。

参考01背包的转移中,我们按照j=v..c[i]的逆序来循环。
这是因为要保证第i次循环中的状态dp[i][v]是由状态dp[i-1][v-c[i]]递推而来。
换句话说,这正是为了保证每件物品只选一次,保证在考虑”选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的子结果dp[i-1][j-c[i]]。
而现在完全背包的特点恰是每种物品可选无限件,所以在考虑”加选一件第i种物品”这种策略时,却正需要一个可能已选入第i种物品的子结果dp[i][j-c[i]]。
所以就可以并且必须采用j=0..v的顺序循环,那么我们也就得到了状态转移方程如下

代码:

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

还有一种证明思路 by http://blog.csdn.net/loi_lxt/article/details/77870376#t7

①dp[i][j]max=dp[i-1][j-k*w[i]]+k*v[i]  0<=k②dp[i][j]max=dp[i-1][j-w[i]-t*w[i]]+t*v[i]+v[i] 0<=t,1<=k              dp[i-1][j]                         k==0dp[i][j-w[i]]max=dp[i-1][j-w[i]-t*w[i]]+t*v[i] 0<=t将③带入②:dp[i][j]max = dp[i-1][j]              dp[i][j-w[i]]+v[i]

多重背包

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

思路:
我们设dp[i][j]表示前i种物品恰放入一个容量为j的背包可以获得的最大价值,在这里与完全背包基本思路设置相同,得到状态转移方程如下:
dp[i][j]=max(dp[i][j],dp[i-1][j-c[i]*k]+w[i]*k); (0<=k<=num[i])

考虑优化:
我们考虑把第i种物品换成若干件物品,使得原问题中第i种物品可取的每种策略—取0..num[i]件,均能等价于取若干件代换以后的物品。
另外,取超过num[i]件的策略必不能出现。

将第i种物品共num[i]个划分成若干件物品,其中每件物品有一个系数,这件物品的费用和价值均是原来单个的费用和价值乘以这个系数。
使这些系数分别为1,2,4,…,2^(k-1),num[i]-2^k+1,且k是满足num[i]-2^k+1>0的最大整数。
对第i种物品整体二进制拆分,将第i种物品分成了log(n[i])种物品。
分成的这几件物品的系数和为num[i],表明不可能取多于num[i]件的第i种物品。
这种方法可以保证对于0…num[i]间的每一个整数,均可以用若干个系数的和表示,
那么对于拆分好的物品进行01背包的选择,就必定可以处理出对第i种物品的所有选择方案。

代码:

void ZeroOnePack(int c,int w){    for(int j=v;j>=c;j--)    dp[j]=max(dp[j],dp[j-c]+w);}void CompletePack(int c,int w){    for(int j=c;j<=v;j++)    dp[j]=max(dp[j],dp[j-c]+w);}void MultiplePack(int c,int w,int num){    if(c*num>=v)//数量足够可看作完全背包    {        CompletePack(c,w);        return;     }    int k=1;    while(k<num)    {        ZeroOnePack(k*c,k*w);        num-=k;        k*=2;    }    ZeroOnePack(num*c,num*w);//num[i]-2^k+1的部分 }for(int i=1;i<=n;i++)MultiplePack(c[i],w[i],num[i]);

混合背包

问题:
就是混合三种背包啦~

思路:
分情况处理即可。

代码:

for(int i=1;i<=n;i++){    if(complete)    CompletePack(c[i],w[i]);    if(zeroone)    ZeroOnePack(c[i],w[i]);    if(multiple)    MultiplePack(c[i],w[i],num[i]);}

二维费用背包

问题:
对于每件物品,具有两种不同的费用,选择这件物品必须同时付出这两种代价。
对于每种代价都有一个可付出的最大值(背包容量),问怎样选择物品可以得到最大的价值。
设第i件物品所需的两种代价分别为a[i]和b[i],两种代价可付出的最大值(两种背包容量)分别为v和u,物品的价值为w[i]。

思路:
加一维状态即可。
我们设dp[i][j][k]表示前i件物品付出两种代价为j和k时可获得的最大价值
dp[i][j]付出代价为i和j时的最大价值
状态转移方程如下:(以01背包为例)

代码:

for(int i=1;i<=n;i++){    for(int j=v;j>=a[i];j--)    {        for(int k=u;k>=b[i];k--)        {            if(j>=a[i]&&k>=b[i])            dp[i][j][k]=max(dp[i-1][j][k],f[i-1][j-a[i]][k-b[i]]+w[i]);            else dp[i][j][k]=dp[i-1][j][k];        }    }}for(int i=1;i<=n;i++){    for(int j=v;j>=a[i];j--)    {        for(int k=u;k>=b[i];k--)        dp[j][k]=max(dp[j][k],dp[j-a[i]][k-b[i]]+w[i]);    }}

最终答案为dp[n][v][u]。
完全背包顺序枚举,多重背包拆分处理即可。

特别的:
有时,”二维费用”的条件是以这样一种隐含的方式给出的:最多只能取m件物品。
这事实上相当于每件物品多了一种”件数”的费用,每个物品的件数费用均为1,可以付出的最大件数费用为M。
那么我们设dp[i][j]表示”付出费用i,最多选j件时可得到的最大价值”处理,其意义同滚动过的上文dp[i][j][k]。
注意:最后在dp[1..v][1..m]范围内寻找答案。

分组背包

问题:
有N件物品和一个容量为v的背包,第i件物品的费用是c[i],价值是w[i]。
这些物品被划分为若干m组,数量分别为siz[i],每组中的物品互相冲突,最多选一件。
求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

思路:
我们设dp[k][j]表示前k组物品花费费用j能取得的最大价值
则对第i组物品策略为选择本组的某一件或是一件都不选,状态转移方程如下

代码:

for(int k=1;k<=m;i++){    for(int j=v;j>=0;j--)    //必须在i层循环之外,保证每一组内的物品最多只有一个会被添加到背包中     {        for(int i=1;i<=siz[k];i++)//i为第k组中的物品        {            if(j>=c[i])            dp[k][j]=max(dp[k-1][j],dp[k-1][j-c[i]]+w[i]);            else dp[k][j]=dp[k-1][j];        }    } }for(int k=1;k<=m;i++){    for(int j=v;j>=0;j--)    {        for(int i=1;i<=siz[k];i++)        {            dp[j]=max(dp[j],dp[j-c[i]]+w[i]);        }    } }

也可以应用同完全背包一样的简单的优化:
对于每组内的物品,若两件物品i,j满足c[i]<=c[j]且w[i]>=w[j],则将物品j去掉,不用考虑。

有依赖的背包问题等其他背包问题暂且搁置。
东隅已逝,桑榆非晚..