背包问题总结

背包问题总结

1.01背包问题

即f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。

则其状态转移方程便是:f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}。


for(int i = 1; i <= n; i++) 
        for(int j = 1; j <= m; j++)
        {
            //  当前背包容量装不进第i个物品,则价值等于前i-1个物品
            if(j < v[i]) 
                f[i][j] = f[i - 1][j];
            // 能装,需进行决策是否选择第i个物品
            else    
                f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
        }           

以上方法的时间和空间复杂度均为O(N*V),其中时间复杂度基本已经不能再优化了,但空间复杂度却可以优化到O(V)。
优化为一维:

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

  


  
     
2.完全背包问题

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


简单有效的优化
完全背包问题有一个很简单有效的优化,是这样的:若两件物品i、j满足c[i]<=c[j]且w[i]>=w[j],则将物品j去掉,不用考虑。


优化为一维:
for(int i = 1 ; i<=n ;i++)
    for(int j = v[i] ; j<=m ;j++)//注意了,这里的j是从小到大枚举,和01背包不一样
        f[j] = max(f[j],f[j-v[i]]+w[i]);


  代码与P01的伪代码只有v的循环次序不同而已。为什么这样一改就可行呢?
  首先想想为什么P01中要按照v=V..0的逆序来循环。
  这是因为要保证第i次循环中的状态f[i][v]是由状态f[i-1][v-c[i]]递推而来
  。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,
  依据的是一个绝无已经选入第i件物品的子结果f[i-1][v-c[i]]。
  而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第i种物品”这种策略时,
  却正需要一个可能已选入第i种物品的子结果f[i][v-c[i]],所以就可以并且必须采用v= 0..V的顺序循环。

4.多重背包问题  将转换成01背包问题那一部分优化了就可以了
                
  把第i种物品拆成费用为c[i]*2^k、价值为w[i]*2^k的若干件物品
  其中k满足c[i]*2^k<V。这是二进制的思想,因为不管最优策略选几件第i种物品,
  总可以表示成若干个2^k件物品的和。这样把每种物品拆成O(log(V/c[i]))件物品。

  
  

    int cnt = 0;     // 将物品重新分组后的顺序
    for (int i = 1; i <= n; i ++)
    {
        int a, b, s;    // a 体积, b 价值, s 每种物品的个数
        scanf("%d %d %d", &a, &b, &s);

        int k = 1;   // 二进制拆分 打包时每组中有 k 个同种物品
        while (k <= s)  // 即y总说的: 最后一组的物品个数 < 2^(n+1)   1 2 4 8 16 ... 2^n 2^(n+1)
        {
            cnt ++;
            v[cnt] = a * k;  // 每组的体积
            w[cnt] = b * k;  // 每组的价值
            s -= k;
            k *= 2;  // 注意是 k * 2,每次增长一倍,不是k * k
        }

        if (s > 0)   // 二进制拆分完之后 剩下的物品个数分为新的一组
        {
            cnt ++;
            v[cnt] = a * s;
            w[cnt] = b * s;
        }
    }


5.混合三种背包问题
  有的物品只可以取一次(01背包),
  有的物品可以取无限次(完全背包),
  有的物品可以取的次数有一个上限(多重背包)

01背包与完全背包的混合
考虑到在P01和P02中最后给出的伪代码只有一处不同,
故如果只有两类物品:一类物品只能取一次,另一类物品可以取无限次,
那么只需在对每个物品应用转移方程时,根据物品的类别选用顺序或逆序的循环即可,复杂度是O(VN)。

for i=1..N
    if 第i件物品是01背包
        for v=V..0
            f[v]=max{f[v],f[v-c[i]]+w[i]};
    else if 第i件物品是完全背包
        for v=0..V
            f[v]=max{f[v],f[v-c[i]]+w[i]};


再加上多重背包
如果再加上有的物品最多可以取有限次,
那么原则上也可以给出O(VN)的解法:遇到多重背包类型的物品用单调队列解即可。
但如果不考虑超过NOIP范围的算法的话,用P03中将每个这类物品分成O(log n[i])个01背包的物品的方法也已经很优了。


6. 二维费用的背包问题

   设这两种代价分别为代价1和代价2,第i件物品所需的两种代价分别为a[i]和b[i]。
   两种代价可付出的最大值(两种背包容量)分别为V和U。物品的价值为w[i]。

算法
费用加了一维,只需状态也加一维即可。设f[i][v][u]表示前i件物品付出两种代价分别为v和u时可获得的最大价值。
状态转移方程就是:f [i][v][u]=max{f[i-1][v][u],f[i-1][v-a[i]][u-b[i]]+w[i]}。
如前述方法,可以只使用二维的数组:当每件物品只可以取一次时变量v和u采用顺序的循环,
当物品有如完全背包问题时采用逆序的循环。当物品有如多重背包问题时拆分物品。


物品总个数的限制
有时,“二维费用”的条件是以这样一种隐含的方式给出的:最多只能取M件物品。
这事实上相当于每件物品多了一种“件数”的费用,每个物品的件数费用均为1,可以付出的最大件数费用为M。
换句话说,设f[v][m]表示付出费用v、最多选m件时可得到的最大价值,则根据物品的类型(01、完全、多重)用不同的方法循环更新,
最后在f[0..V][0..M]范围内寻找答案。

另外,如果要求“恰取M件物品”,则在f[0..V][M]范围内寻找答案。

事实上,当发现由熟悉的动态规划题目变形得来的题目时,在原来的状态中加一纬以满足新的限制是一种比较通用的方法。


7.分组背包问题

设f[k][v]表示前k组物品花费费用v能取得的最大权值,则有f[k][v]=max{f[k-1][v],f[k-1][v-c[i]]+w[i]|物品i属于第k组}。

集合划分依据:根据从第i组物品中选哪个物品进行划分.
f[i][j] = max(f[i][j], f[i - 1][j - v[i][k]] + w[i][k]);


 for(int i=1;i<=n;i++)
    for(int j=0;j<=m;j++)
    {
        f[i][j]=f[i-1][j];  //不选 不选表示不选第 i 组物品的所有物品,只从前 i−1 组物品里面选
        for(int k=0;k<s[i];k++)
            if(j>=v[i][k])     
                f[i][j]=max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]);  
    }        


优化为一维:
 for(int i=0;i<n;i++)
    for(int j=m;j>=0;j--)
        for(int k=0;k<s[i];k++)    //for(int k=s[i];k>=1;k--)也可以
            if(j>=v[i][k])     
                f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);  

8.有依赖的背包问题
   我们可以对主件i的“附件集合”先进行一次01背包,得到费用依次为0..V-c[i]所有这些值时相应的最大价值f'[0..V-c[i]]。
   那么这个主件及它的附件集合相当于V-c[i]+1个物品的物品组,其中费用为c[i]+k的物品的价值为f'[k]+w[i]。
   也就是说原来指数级的策略中有很多策略都是冗余的,通过一次01背包后,将主件i转化为 V-c[i]+1个物品的物品组,
   就可以直接应用P06的算法解决问题了。

9.泛化物品 :一个泛化物品就是一个数组h[0..V],给它费用v,可得到价值h[V]。


动态规划的时间复杂度 ≈≈ 问题的总个数 × 问题要做出的选择数

  如, 对于 01 背包问题, 问题的总个数为N⋅V (N 为物品个数, V 为背包容量), 问题要做出的选择数为 2(选或不选)

  则 01 背包问题的时间复杂度约为 2N⋅V

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值