动态规划之背包

1. 0 1 背包

  • 什么是 0 1 背包?
    有 n 个物品和容量是 v 的背包,每件物品只能选一次,第 i 件物品的体积是 v[i],价值是 w[i],
    求放物品进入背包后,体积不超过,但是价值最大。
  • 状态转移方程
    dp[i][j]表示从1 到 i 中选择,总体积不超过 j 的最大价值。
    然后我们可以思考 dp[i][j] 是从哪里来的?思考 第 i 件物品可以选也可以不选,就有两种情况,
    dp[ i ][ j ] = dp[ i - 1 ][ j ]
    dp[ i ][ j ] = dp[ i - 1 ][ j - v[ i ] ] + w[ i ]
    故 dp[ i ][ j ] = max ( dp[ i - 1 ][ j ] , dp[ i - 1 ][ j - v[ i ] ] + w[ i ] )
#include<iostream>
#include<algorithm>

using namespace std;
const int N = 1010;
int n,m;
int v[N],w[N];
int f[N][N];

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];
        if(j >= v[i])
            f[i][j] = max(f[i][j],f[i - 1][j - v[i]] + w[i]);
    }
    cout<<f[n][m]<<endl;
    return 0;
}
  • 优化( 一维数组 )
    我们再来观察状态方程 dp[ i ][ j ] = max ( dp[ i - 1 ][ j ] , dp[ i - 1 ][ j - v[ i ] ] + w[ i ] ),
    dp[ i ][ j ] 只于第 i - 1 层的数据有关,所以我们可以用一维数组,就是控制 i ,一层层来更新直至更新到 n 层,
    此时我们要注意我们要从后往前,因为一层层从前往后更新的话,我们用的 dp[ i - 1 ][ j - v[ i ] ] 会在第 i层首先会被更新,就不正确了
    而第二层循环我们只需要考虑 j >= v[ i ] 的情况。
#include<iostream>
#include<algorithm>

using namespace std;
const int N = 1010;
int n,m;
int v[N],w[N];
int f[N];

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[j],f[j - v[i]] + w[i]);
    }
    cout<<f[m]<<endl;
    return 0;
}

1. 完全 背包

  • 什么是 完全 背包?
    有 n 个物品和容量是 v 的背包,每件物品有无限件可用,第 i 件物品的体积是 v[i],价值是 w[i],
    求放物品进入背包后,体积不超过,但是价值最大。
  • 状态表示
    f[ i ][ j ] 表示前 i 个物品,且总体积不大于 j 的所有选法的最大值
  • 状态计算
    参考 0 1 背包,思考 f[i][j] 是从哪里来的?第 i 件物品可以选 0 个,1个 .....k个 ,取最大值即可
    f [ i ][ j ] = f [ i - 1,j - k * v [ i ] ] + k * w [ i ]
#include<iostream>
#include<algorithm>

using namespace std;
const int N = 1010;
int n,m;
int v[N],w[N];
int f[N][N];

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 ++)
             for(int k = 0;k* v[i] <= j;k ++)
    {
            f[i][j] = max(f[i][j],f[i - 1][j - v[i] * k] + k * w[i]);
    }
    cout<<f[n][m]<<endl;
    return 0;
}
  • 优化
    f [ i ][ j ] = max(f [ i ][ j ] , f [ i - 1 ][ j - v [ i ] * k ] + k * w [ i ]); k = 0 ,1,2....
    f[i][ j - v[ i ] ] 展开发现 f [ i ][ j ] = f[i][ j - v[ i ] ] + w
#include<iostream>
#include<algorithm>

using namespace std;
const int N = 1010;
int n,m;
int v[N],w[N];
int f[N];

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 = v[i];j <= m;j ++)
            f[j] = max(f[j],f[j - v[i]] + w[i]);
    cout<<f[m]<<endl;
    return 0;
}

3.多重 背包

  • 什么是 多重 背包?
    有 n 个物品和容量是 v 的背包,每件物品最多有s件,第 i 件物品的体积是 v[i],价值是 w[i],
    求放物品进入背包后,体积不超过,但是价值最大。
  • 状态表示
    f[ i ][ j ] 表示前 i 个物品,且总体积不大于 j 的所有选法的最大值
  • 状态计算
    参考 0 1 背包,思考 f[i][j] 是从哪里来的?第 i 件物品可以选 0 个,1个 .....k个 ,取最大值即可
    f [ i ][ j ] = f [ i - 1,j - k * v [ i ] ] + k * w [ i ] 朴素版本的完全背包
#include<iostream>
#include<algorithm>

using namespace std;
const int N = 1010;
int n,m;
int v[N],w[N],s[N];
int f[N][N];

int main()
{
    cin >> n >> m;
    for(int i = 1;i <= n;i ++)
        cin>>v[i]>>w[i]>>s[N];
    for(int i = 1;i <= n;i ++)
        for(int j = 0;j <= m;j ++)
             for(int k = 0;k <= s[i] && k* v[i] <= j;k ++)
    {
            f[i][j] = max(f[i][j],f[i - 1][j - v[i] * k] + k * w[i]);
    }
    cout<<f[n][m]<<endl;
    return 0;
}
  • 优化:二进制优化
    不可以用完全背包的方法了,我们可以展开,会发现最后多出来一项,没办法求解
    二进制优化就是转化成0 1 背包的问题,比如某一物品有1023件,我们可以分成若干组,
    第一组1,第二组2,第三组4,第四组8····一直到加起来等于1023,如果没办法凑整,到了2 ^ k,但是2 ^ k +1 超出总个数了,
    我们就直接计算一个常数,s - 2 ^ k,转化成要不要选某一组的0 1背包的问题。
posted @ 2023-03-26 22:56  焦糖玛奇果  阅读(54)  评论(0)    收藏  举报