• 动态规划_01背包_从Dijikstra和Floyd入手,彻底理解01背包


     dp一直是短板,现在从最基础的地方开始补

    给定背包总容量 M ,n个商品选择,分别有价值vi,占量wi,从中取商品放入背包,令。容量和W=Σwi不超过M,令背包中的价值和V=Σvi最大

     然后取法有很多种,分别是

    1. 只取一次
    2. 各种变形,比如:可重复取,只能取有限次。

    参考博文:链接

     对于最基本的情况1    贴一个vjudge的题目链接

    如果暴力做的话就是求全组合,然鹅数据量实在不够,如果穷举就给一直TE了

    这个问题最讨厌的地方在于,不能直接使用贪心排序,无论是重量贪心还是v/w贪心都不行,

    具体可以参考这篇博文,对各种贪心方式和最优解直接的差距做了一个趋近误差分析,https://blog.csdn.net/weixin_33825683/article/details/94745157 

    不过,前面的选择会影响后面的,每个物体的优先权会被整体的情况影响,这是不是很耳熟?

    01背包的—只取一次—的情况,与最短路很相似,

    • 01背包前面的商品会影响后面的,最短路前面选择的节点会影响后面的节点,
    • 01背包要求价值最大,最短路要求路径长度最小
    • 01背包每次决策是否放入某物品,最短路每次决策是否放入某个节点

    可以把物品看成节点

    一点区别,

    • 01背包限制物品总重量不能超过M,不要求两物品之间要有什么联系
    • 最短路选边的限制是两节点间是否存在边,不要求节点选择有限,比如只能选几个节点之类的

    回忆解决最短路的算法,可以说,Floyd和Dijikstra,其实就是动态规划解决最短路问题

    • Floyd是把每一个点到其它点的最短路径记录下来,从子问题,逐层扩展,不断更新邻接表的值
    • Dijikstra是确定起点,把该起点到其他位置的最短路都找到,然后从子问题,逐层扩展,不断更新邻接表的值

    Floyd的子问题更新方式:dp【i】【j】=min(dp【i】【k】+dp【k】【j】,dp【i】【j】),外面是三重循环,O(n2)

    • dp【i】【j】表示已有的不放入k节点的   i - j   最短路径,
    • k 表示是否在   i - j 之间放入k节点,放进去时,新路径 R=dp【i】【k】+dp【k】【j】

    Dijikstra的子问题更新方式 ,dp【i】=min(dp【j】+ r【j】【i】,dp【i】)

    • dp【i】表示从起点到 i 最短路径 ,r【j】【i】表示已知 j - r 的直接路径,
    • 确定起点,不需更新完所有的最短路,而是,只更新分支树

    可以发现,01背包和Floyd非常像,因为它需要把整张路径邻接表都初始化

    仿照以上思路,把物品看成节点,要求V最大,V/M最大,即总容量的利用率最高,即M分配给不同的 wi,要求每一份子空间的,V最大

        01背包每次决策是否放入某物品,最短路每次决策是否放入某个节点

    可以这样写:dp【j】=max(dp【j】,dp【j-w【i】】+ v【i】),j  》= w【i】 ; j = m 从大到小,或者,m=0从小到大遍历完,决策时 i 为常数, 所以 i 在最外层

    • dp【j】表示 空间 j  时 最大价值(不需要装满,只要能放进去就检查一次),同理dp【j-w【i】】表示不放入 dp【i】
    • i 表示第 i 件物品是否放进去容量上限为   j  的背包,每件物品从1到m都测一次;
    • 放进去时,新价值 V=dp【j-w【i】】+v【i】,dp【m】为最终结果

    也可以这样写,比较常见的二维数组写法:dp【i】【j】=max(dp【i】【j-1】,dp【i-w【j】】【j-1】+ v【j】)j《  n,j  和 上面的 i 是一个道理

    • dp【i】【j】表示空间为 i  时,放入第 j 件物品 , 得到的最大价值(不需要装满)的效果
    • j   表示 第 j 件 物品是否放进去背包   ,第 j  件恰好也是 第 j 号  ,放进去时,新价值 V=dp【i-w【j】】【j-1】+ v【j】,
    • 这个好处是可以比较方便地记录下放入的背包编号,只需要另设一个数组 ,令 r【j】= k ,不断更新即可

    注意枚举的顺序是正序还是逆序, https://blog.csdn.net/yandaoqiusheng/article/details/84929357

    • j = m 从大到小,到 wi
    • j = 0 从小到大,到 m

    AC代码,写法1,实际实现的时候,需要把dp【j】从 j=m 到 j=0,j-- 一直试下去;j = m 从大到小(m,m-1,m-2,,,m-x=wi)比从小到大排好,从小到大还要循环内再判断

     1 #include <iostream>
     2 #include <cstring>
     3 #include <cstdio>
     4 using namespace std;
     5 const int M=1e5+10;
     6 int w[M],v[M];
     7 int n,m;
     8 int dp[M];
     9 
    10 int T;
    11 void f() {
    12     memset(dp,0,sizeof(dp));
    13     //dp[j]=max(dp[j],dp[j-w[i]]+v[i]);i(1,n),j>=w[i],
    14     //容量初始值j=m
    15     //决策时i为常数, 所以 i 在最外层
    16     for(int i=1; i<=n; i++) {
    17         for(int j=m; j>=w[i]; j--) { //能取等!!!
    18             dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
    19         }
    20     }
    21     cout<<dp[m]<<endl;
    22 }
    23 /*
    24 5 1000
    25 144 990
    26 487 436
    27 210 673
    28 567 58
    29 1056 897
    30 
    31 2099
    32 */
    33 int main() {
    34 
    35     cin>>n>>m;
    36     for(int i=1; i<=n; i++)cin>>w[i]>>v[i];
    37     f();
    38 
    39     return 0;
    40 }
    View Code

    不能AC的写法2,数据量太大 ,二维会爆炸

     1 #include <iostream>
     2 #include <cstring>
     3 #include <cstdio>
     4 using namespace std;
     5 const int M=1e5+3;
     6 int w[M],v[M];
     7 int n,m;
     8 int dp[M][503];
     9 
    10 void f() {
    11     memset(dp,0,sizeof(dp));
    12     //dp[i][j]=max(dp[i][j-1],dp[i-w[k]][j-1]+v[k]);i(1,n),i>w[k],
    13     //容量初始值i=m
    14     //决策时i为常数, 所以 i 在最外层
    15     int ans=0;
    16     for(int j=1; j<=n; j++) {
    17         for(int i=1; i<=m; i++) {
    18             if(i>w[i])dp[i][j]=max(dp[i][j-1],dp[i-w[j]][j-1]+v[j]);
    19             else dp[i][j]=dp[i][j-1];
    20 
    21         }
    22 
    23     }
    24 
    25     cout<<dp[m][n]<<endl;
    26 }
    27 
    28 /*
    29 5 1000
    30 144 990
    31 487 436
    32 210 673
    33 567 58
    34 1065 897
    35 
    36 2099
    37 */
    38 int main() {
    39 
    40     cin>>n>>m;
    41     for(int i=1; i<=n; i++)cin>>w[i]>>v[i];
    42     f();
    43     return 0;
    44 }
    View Code

    这种情况不能按照    i = m 从大到小(m,m-1,m-2,,,m-x=wi)否则,更新是不完全,需要将dp【m】【1-n】都检查一次,不能直接通过输出dp【m】【n】来决定,

    原因在于,此时dp【i】【j】从 j=1 到 j=n有多个值,个别n可能并没有数值,而写法1直接就是这些情况中的最大值,所以不用再找

    老实一点,可爱多了
  • 相关阅读:
    设计模式-代理模式
    设计模式-策略模式
    设计模式-单例模式
    优先队列
    n!中质因子个数
    计算组合数
    高精度
    memset用法
    质因子分解
    素数筛法
  • 原文地址:https://www.cnblogs.com/KID-yln/p/12642341.html
Copyright © 2020-2023  润新知