zoukankan      html  css  js  c++  java
  • 背包九讲_完全背包问题

    问题简介:

    有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。

    第 i 种物品的体积是 v [ i ],价值是 w [ i ]

    求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
    输出最大价值。

    有N件物品和一个容量为V的背包。第i件物品的体积是v[i],价值是w[i]。求解在不超过背包容量的情况下价值最大数。

    每种物品有无限个(假设第 i 个背包有K个  意味着对于第 i 个背包我们就有K+1中选择)

    直接上一维空间:

    ----------------------------------------------------------------------------------------------------------

    符号说明:F [ i ]表示在使用体积 j 后 的Worth_Max;

    --------------------------------------------------------------------------------------------------------

    结果:

      res=max (   f [ 0,1,……,m ]   );

    状态转移问题:

      for (int i =0;i<n;++i)  //  第一层循环,表示对于第 i 个背包处理

        for (int j = m ; j>v [ i ] ; j - -)

          for ( int k =0 ; k * v [ i ] < = j ;k ++)

            f [ j ]= max ( f [ j ] , f [ j - k * v [ i ] + k * w [ i ] ; 

    --------------------------------------------------------------------------------------

    状态分析:

        for (int j=v [ i ];j<= m  ; j++)   //第二层循环表示对于第 i 个背包来说,可用空间的改变带来的状态变化;

        f [ j ]=max ( f [ j ] , f [ j -v [ i ] ] + w [ i ] ]) ;,整个实际上是不怎么好的啊

    状态方程说明: 

      第二层循环的处理:

      因为若不改变第二层循环则原第二层循环为:  for ( int j = m ; j > v [ j ] ; j --);

    注意此时的这个的第 i 个背包的体积大于 0;此时第二次循环还是从大到小的话,那么我在进行状态转移的时候 ,计算 f [ j ] 的时候,f [ j ]=max ( f [ j ] , f [ j -v [ i ] ] + w [ i ] ]) ;

    这个时候会有  f [ j -v [ i ] ] + w [ i ] ]) 实际上是一个新的状态,这个状态并不在 f [ j ]里面。这就相当于所有背包就只用了一次,并没有达到我无限次的想法;

    ----------------------------------------

    基本思路

    明显的动态规划问题:

    即:F [ i ] [ j ] 的状态仅仅与之间的一个状态有关;

    状态方程:

                       1. 选第 i 件物品:F [ i ] [ j ] = F [ i ] [ j ]

                       2. 不选第 i 件物品:F [ i ] [ j ] =  F [ i-1 ] [ j -v [ i ] ] //  关于这个 j - v [ i ] 的说明,就是说在这个时候我不选第 i 件物品,自然这个空间就腾出来了

    状态转移方程:

                       F [ i ] [ j ] = max {  F [ i ] [ j ]  ,F [ i-1 ] [ j -v [ i ] ]  } //  就是这两个方程中选一个大的出来就行

    代码详述(优化之前):

    #include<iostream>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int N = 1010;
    int n, m;//有 n 个背包,总体积为 m
    int f[N][N];//存放状态, 
    int v[N], w[N];//分别表示第 i 个物品的体积与价值

    int main()
    {
    cin >> n >> m;//数据录入
    for (int i = 1; i <= n; i++) cin >> v[i] >> w[i];
    //状态转移
    for(int i =1;i<=n;++i)
    for (int j = 0; j <= m; j++)
    {
    f[i][j] = f[i - 1][j];//不选第 i 个背包
    if(j>=v[i])//选第 i个背包之前要做一次特判
    f[i][j] = max(f[i][j],f[i - 1][j - v[i]]+w[i] );//注意使用max函数的时候是使用( ) 不是 { }
    }
    int res = 0;
    for (int j = 1; j <= m; ++j)
    res = max(res, f[n][j]);
    cout << res << endl;
    }
    /*
    搜索,动归问题都是这个状态问题,状态才是根本
    在写状态转移方程的时候:
    1.先找到到底什么才是限制条件,这个里面背包的体积才是限制条件,所以它就是状态转移方程里的那个变量;
    2.找到第 i 个状态和第 i-1 个状态之间的关系啊;(特别注意是否需要预判)
    */

    动态规划时间复杂度分析:

    状态数:二维数组存放状态(因为每个状态都会检索所以N平方)

    分析状态转移方程中对状态的处理,没有循环,即为线性处理,C

    综上:C*N平方

    优化空间复杂度

    代码详述:

    #include<iostream>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int N = 1010;
    int n, m;//有 n 个背包,总体积为 m
    int f[N];//存放状态,表示体积为N的时候,价值最大数 
    int v[N], w[N];//分别表示第 i 个物品的体积与价值

    int main()
    {
    cin >> n >> m;//数据录入
    for (int i = 1; i <= n; i++) cin >> v[i] >> w[i];
    //状态转移
    for(int i =1;i<=n;++i)
    for (int j =m ; j >=v[i]; j--)
    f[j] = max(f[i],f[j - v[i]]+w[i] );//注意使用max函数的时候是使用( ) 不是 { }
    cout << f[m] << endl;
    }
    /*
    搜索,动归问题都是这个状态问题,状态才是根本
    在写状态转移方程的时候:
    1.先找到到底什么才是限制条件,这个里面背包的体积才是限制条件,所以它就是状态转移方程里的那个变量;
    2.找到第 i 个状态和第 i-1 个状态之间的关系啊;(特别注意是否需要预判)
    */

    关于代码二的思路,下一次训练再写

    -----------------------------------------------------------------------------------------------------------------------------------------------------------

    以上方法的时间和空间复杂度均为O(N*V),其中时间复杂度基本已经不能再优化了,但空间复杂度却可以优化到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]]的值。伪代码如下:

    for i=1..N

    for v=V..0

    f[v]=max{f[v],f[v-c[i]]+w[i]};

    其中的f[v]=max{f[v],f[v-c[i]]}一句恰就相当于我们的转移方程f[i][v]=max{f[i-1][v],f[i- 1][v-c[i]]},因为现在的f[v-c[i]]就相当于原来的f[i-1][v-c[i]]。如果将v的循环顺序从上面的逆序改成顺序的话,那么则成了f[i][v]由f[i][v-c[i]]推知,与本题意不符,但它却是另一个重要的背包问题P02最简捷的解决方案,故学习只用一维数组解01背包问题是十分必要的。

  • 相关阅读:
    ORACLE游标概念讲解
    [转载]Oracle如何查看日志
    oracle 游标以及游标变量
    Oracle游标大全
    [转载]Oracle如何查看日志
    追踪从指缝溜走的时间
    《Falcon 初印象》幻灯分享
    百姓网那道题
    一个JavaScript写的黑白棋AI
    trac 推广 ppt 分享
  • 原文地址:https://www.cnblogs.com/WAsbry/p/12650718.html
Copyright © 2011-2022 走看看