背包问题小结

DP算法之背包问题小结

来自cainiao乱写的笔记

1)01背包问题

朴素算法下的状态转移方程为:f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i])
由于每层循环只用到了上一层的i,故而可以优化为一维,但是由于j-v[i]严格小于j,即计算f[j]时,f[j-v[i]]已经算过,会被覆盖,所以枚举j时需要从大到小。
所以最终的状态转移方程为:f[j] = max(f[j], f[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];

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;
}

2)完全背包问题

朴素算法下的状态转移方程为:f[i][j] = max(f[i][j],f[i-1][j-k*v[i]] + k*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-k*v[i]]+k*w[i]);

若优化到二维:

我们知道:
f[i][j]=max(f[i-1][j],f[i-1][j-v]+w,f[i-1][j-2v]+2w....);
f[i][j-v]=max( f[i-1][j-v], f[i-1][j-2v]+w....);

我们发现可以使用下面的f[i][j-v[i]]+w[i]替换上面的后半部分式子
状态转移方程:f[i][j]=max(f[i-1][j],f[i][j-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][j-v[i]]+w[i]);
        }

再次优化到一维:

不同于01背包问题的是,不需要从大到小遍历,直接遍历即可。
状态转移方程:f[j]=max(f[j],f[j-v[i]+w[i);
该情况下核心代码为:

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

3)多重背包问题

朴素算法下的状态转移方程为:f[i][j] = max(f[i][j],f[i-1][j-v[i]*k] +k*w[i]);
该情况下核心代码为:

    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]);

在多重背包中:
f[i , j ] = max( f[i-1,j] ,f[i-1,j-v]+w ,f[i-1,j-2v]+2w ,..... f[i-1,j-Sv]+Sw, );
f[i , j-v]= max( f[i-1,j-v] ,f[i-1,j-2v]+w, ..... f[i-1,j-Sv]+(S-1)w, f[i-1,j-(S+1)v]+Sw );
可以发现由于选择个数的限制多出了一项f[i-1,j-(S+1)v]+Sw
所以该题我们用到了二进制优化将时间复杂度从O(n^3)降到O(n^2logS)

该思想为将大量物品分别以1,2,4,8,···,2^k,C的数量分别打包,而后将问题看作01背包去解决。
例如:将200划分为1,2,4,8,16,32,64,73的组合前七个数可构成0-127之间的所有数,加上最后的73,
即可构成0-200的所有数。但前提条件是要满足`c<2^(k+1)`

完整代码为:

#include<iostream>
using namespace std;

const int N = 12010, M = 2010;

int n, m;
int v[N], w[N]; 
int f[M]; 

int main()
{
    cin >> n >> m;
    int cnt = 0; //分组的组别
    for(int i = 1;i <= n;i ++)
    {
        int a,b,s;
        cin >> a >> b >> s;
        int k = 1; // 组别里面的个数
        while(k<=s)
        {
            cnt ++ ; //组别先增加
            v[cnt] = a * k ; //整体体积
            w[cnt] = b * k; // 整体价值
            s -= k; // s要减小
            k *= 2; // 组别里的个数增加
        }
        //剩余的一组
        if(s>0)
        {
            cnt ++ ;
            v[cnt] = a*s; 
            w[cnt] = b*s;
        }
    }

    n = cnt ; //枚举次数正式由个数变成组别数

   /*****01背包问题的一维优化*****/
    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;
}

4)分组背包问题:

朴素算法下的状态转移方程为:f[j] = max(f[j],f[j-v[i][k]]+w[i][k]);

完整代码为:

#include<iostream>
#include <algorithm>
using namespace std;

const int N = 110;

int n,m;
int v[N][N],w[N][N];
int f[N];
int s[N];

int main(){
    cin >> n >> m;
    for(int i = 1;i<=n;i++)
    {   cin>>s[i];
        for(int j = 0;j<s[i];j++)
            cin>>v[i][j]>>w[i][j];
    }

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

    return 0;
}

Tips:枚举 j 时,如果用到了上一层元素则从大到小枚举(01背包),否则从小到大枚举即可。

最后,放一块看看吧:

1)f[i][j]= max(f[i][j],f[i-1][j-v[i]]+w[i]);  
  f[j] = max(f[j], f[j - v[i]] + w[i]);
2)f[i][j] = max(f[i][j],f[i-1][j-k*v[i]] + k*w[i]);
  f[i][j] =max(f[i-1][j],f[i][j-v[i]]+w[i]);
  f[j]=max(f[j],f[j-v[i]+w[i);
3)f[i][j] = max(f[i][j],f[i-1][j-v[i]*k] +k*w[i]);
4)f[j] = max(f[j],f[j-v[i][k]]+w[i][k]);

完结!!!

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值