背包问题笔记

背包问题

对于背包问题这一类典型的动态规划问题,一般而言f[i]表示背包容量为i的时候能装入物体的最大价值

01背包

每个物品只能选择一次

int f[MAXN];
memset(f, 0, sizeof(f));
for (int i = 1; i <= n; i++) {
    for (int j = W; j >= w[i]; j--) {
        f[j] = max(f[j], f[j - w[i]] + v[i]);
    }
}

完全背包

每个物品可以选择无限次

int f[MAXN];
memset(f, 0, sizeof(f));
for (int i = 1; i <= n; i++) {
    for (int j = w[i]; j <= W; j++) {
        f[j] = max(f[j], f[j - w[i]] + v[i]);
    }
}

多重背包

每一种物品只有 k i k_i ki

二进制分组优化,把每一件物品拆分成多件,用01背包求解即可

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-24V3tpnB-1690261757362)(image/经典动态规划/1690187935597.png)]

int index = 0;
for (int i = 1; i <= m; i++) {
    int w, v, k;
    cin >> w >> v >> k;
    for (int c = 1; c <= k; c <<= 1) {
        k -= c;
        ww[index] = c * w;
        vv[index++] = c * v;
    }
    if (k) {
        ww[index] = k * w;
        vv[index++] = k * v;  
    }   
}

单调队列优化

for (int i = 1; i <= n; i++) {
    memcpy(g, f, sizeof(f));
    cin >> w >> v >> s;// 体积 价值 数量
    for (int j = 0; j < w; j++) {// 优化枚举背包容量的这一维
        // 根据模w的值不同拆分成w个类
        int h = 0, t = -1;//代表队列的头和尾
        for (int k = j; k <= m; k += v) {// m表示最大容量
            if (h <= t && q[h] < k - s * w) h++; //维护单调队列
            if (h <= t) f[k] = max(g[k], g[q[h]] + (k - q[h]) / w * v);// 更新
            //当前值比队尾更有价值队尾则出队
            while (h <= t && g[k] >= g[q[t]] + (k - q[t]) / w * v) t--;
            q[++t] = k;
        }
    }
}
cout << f[m];

混合背包

也就是01,完全,多重背包的混合,也就是说有的物品只能选或不选,有的可以选任意多次,而有的有一定限制

for (循环物品种类) {
    if (是 0 - 1 背包)
        套用 0 - 1 背包代码;
    else if (是完全背包)
        套用完全背包代码;
    else if (是多重背包)
        套用多重背包代码;
}

二维费用背包

和01背包实际上基本一致,但是选一个物品会消耗两种价值
实际上,只需要在状态中增加一个维度用于存放第二种价值即可

for (int k = 1; k <= n; k++) // 仍然是先枚举物品
    for (int i = m; i >= mi; i--)    // 对经费进行一层枚举
        for (int j = t; j >= ti; j--)  // 对时间进行一层枚举
        dp[i][j] = max(dp[i][j], dp[i - mi][j - ti] + 1);

分组背包

和01背包类似,但是在每一个组中,只能选择一件物品
解决方法:对每一个组进行一次01背包
存储方式:t[k][i]表示第k组第i件物品的编号,cnt[k]表示第k组有多少物品

for (int k = 1; k <= ts; k++)    // 循环每一组(对应01中的选取物品)
    for (int i = m; i >= 0; i--) // 循环背包容量(从大到小)
        for (int j = 1; j <= cnt[k]; j++)   // 循环该组的每一个物品(即决策哪一个要选)
        if (i >= w[t[k][j]])     // 背包容量充足
            dp[i] = max(dp[i], dp[i - w[t[k][j]]] + c[t[k][j]]);  // 像0-1背包一样状态转移

有依赖的背包

分类讨论对于一个主件和它的若干个相关附件
买主件 + {附件的一个子集}
所以可以看作是一个分组背包,因为只有可能是以上诸多可能性中的一种
注: 对于多叉树而言,先要计算子节点集合再计算父节点集合

求背包问题的方案数

以01背包为例,对于其他背包也相似地

  1. f [ i ] f[i] f[i]表示最大价值的同时,用 c [ i ] c[i] c[i]表示最优选法的方案数, c [ i ] c[i] c[i]初始化为1
    f[i]表示背包容量为i的时候能装入物体的最大价值
cin >> n >> m;
for (int i = 0; i <= m; i++) {
    c[i] = 1;
}
for (int i = 1; i <= n; i++) {
    cin >> v >> w;
    for (int j = m; j >= w; j--) {
        if (f[j - w] + v > f[j]) {
            f[j] = f[j - w] + v;
            c[j] = c[j - v];
        }
        else if (f[j - w] + v == f[j]) {
            c[j] = (c[j] + c[j - w]) % mod;
        }
    }
}
  1. 如果要求刚好装满背包的话,那么f数组的定义应该有所变化
    f[i]表示恰好装满情况下的装入物体的最大价值,实际上,只需要修改初始化条件即可
for (int i = 1; i <= m; i++) f[i] = INT_MIN;
f[0] = 0, c[0] = 1;

背包问题求具体方案

注:此时还要求选取的为最小字典序的序列
此时f[i][j]表示i到n件序号的物品用于装j容量的背包所能装最大值

int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
    cin >> v[i] >> w[i];
}
for (int i = n; i >= 1; i--) {
    for (int j = 0; j <= m; j++) {
        p[i][j] = j;
        f[i][j] = f[i + 1][j];
        if (j - v[i] >= 0)
            f[i][j] = max(f[i + 1][j], f[i + 1][j - v[i]] + w[i]);
        if (j - v[i] >= 0 && f[i][j] == f[i + 1][j - v[i]] + w[i]) 
            p[i][j] = j - v[i];
    }
}
int j = m; 
for (int i = 1; i <= n; i++) {
    if (p[i][j] < j) {
        cout << i << ' ';
        j = p[i][j];
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

魈宝不想写程序

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值