背包问题

01背包
二维数组代码

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

一维代码

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

因为要确保每一个物品只是用一次,从大到小遍历的话就保证了物品不会重复选取

完全背包问题(每个物品不限制选取次数)

TLE代码,暴力 O(N * M * K)

    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= m; j ++){
            dp[i][j] = dp[i-1][j];
            for(int k = 1; k * v[i] <= j; k ++)
                dp[i][j] = max(dp[i][j], dp[i-1][j - k * v[i] ] + k * w[i]);
        }
        
    cout << dp[n][m] << endl;

二维变一维代码

为什么可以从该点直接遍历呢
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 ]         , f [ i - 1][j - 2v] +   w, … f [ i - 1][j - sv] + (s - 1)w,

中间都是相同的,只是全多了一个w,还有即使第一项
so : f [ i ][ j ] = max (f[ i - 1][ j ], f[ i ][ j - v ] + w)

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

多重背包(每个物品限制选取次数)

TLE代码,暴力 O(N * M * K)

    for(int i = 1;i <= n;i ++)
        for(int j = 0;j <= m;j ++){
            dp[i][j] = dp[i-1][j];
            for(int k = 1;k <= s[i] && k * v[i] <= j; k ++)
                dp[i][j] = max(dp[i][j], dp[i-1][j - k*v[i]] + k * w[i]);
        }
        
    cout << dp[n][m] << endl;

这样的复杂度实在是太高了,那么能不能用上面的方法进行优化呢
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 ]         , f [ i - 1][j - 2v] +   w, … f [ i - 1][j - sv] + (s - 1)w, f [ i - 1][j - (s + 1)v] + s w,

因为每个物品限制了个数,而这个个数并不是体积的临界值,所以f [ i ][ j ] 和 f [ i ][ j - v ] 都有可能是选择s个物品,这样的话就看最后一个值能不能继续转化

多了最后的一项,
知道一个序列的最大值后,去掉最后一项后前面的最大值可知吗
答案是不可知的,

无法利用上面的方法进行优化

那么怎么办呢

比如对于一个物品来说,最多选s个,那么有可能选的即为0~s这里面的
原来的方法是for一遍,可是这样的效率太低了,

那么就是怎么表示这0~s这些数字,不难想到是二进制啊,
之前的 lca算法 也是,由于挨个遍历效率太低了,所以就用二进制进行跳跃遍历

把这个数拆解成二进制,然后用这些二进制数可以表示所有的小于等于该数的数

转换为二进制后,发现每个新的背包要么选择,要么不选择,那么就是01背包了

把多重背包变换为二进制的01背包,在这些二进制的01背包内进行选择,

这样就是利用消耗空间去换取时间

比如 s = 13,那么怎么进行二进制匹配呢,
1 2 4 5,为什么这样是对的呢,因为1 2 4 可以表示 0 ~ 9 内所有的数,而 0 ~ 9 全部加上 5 即可表示 5 ~ 13 内所有的数, 这样 1 2 4 5 就可以表示0 ~ 15 内所有的数字了

N(新背包的种类) = n(原背包的种类) * log s(对于每个原背包最大的个数进行二进制匹配)

int main(){
    cin >> n >> m;
    while(n --){
        int a, b, c;
        cin >> a >> b >> c;
        int t = 1;
        while(c >= t){//二进制匹配
            c -= t;
            cnt ++;
            w[cnt] = t * b;
            v[cnt] = t * a;
            t *= 2;
        }
        if(c){//对于剩下的数存在一个背包内
            cnt ++;
            w[cnt] = c * b;
            v[cnt] = c * a;
        }
    }
    
    for(int i = 1; i <= cnt; i ++)
        for(int j = m; j >= v[i]; j --){
            dp[j] = max(dp[j], dp[j-v[i]] + w[i]);
        }
        
    cout << dp[m];
    return 0;
}

分组背包

每组内只选择一个物品,每一次选择物品的时候都是由上一层转化来,这样就保证了每一层只选一个

二维代码

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

一维代码

    for(int i = 1; i <= n; i ++)
        for(int j = m; j >= 0; j --)
            for(int k = 1; k <= s[i]; k ++)
                if(j >= v[i][k]) //体积得符合要求
                    dp[j] = max(dp[j], dp[j - v[i][k]] + w[i][k]);

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值