背包问题

背包问题总结

Index

01背包

题目描述:有 N 件物品和一个容量是 V的背包。每件物品只能使用一次。第 i件物品的体积是 vi,价值是 wi。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

输出最大价值。

思路:动态规划,对于每一件物品遍历背包容量,当背包可容纳值大于等于当前物品,与之前已放进去的物品所得价值进行对比,考虑把是否需要置换。

状态转移方程:定义dp[i][j]:前i个物品,背包容量j下的最优解

  • (1)当前背包容量不够,为前𝑖−1个物品最优解:j< w[i]时,有 dp[i][j] = dp[i-1][j]
  • (2)当前背包容量够,判断选还是不选第i个物品:j >= w[i]时, 选该物品: dp[i][j] = dp[i-1][j-w[i]] + v[i] 不选该物品: dp[i][j]=dp[i-1][j]
for (int i = 1; i <= n; ++i) {
    for (int j = 0; 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]);
    }
}
int ans = 0;
for (int i = 0; i <= m; ++j) 
  ans = max(ans, dp[n][i])

一维优化

for (int i = 1; i <= n; ++i) {
    for (int j = m; j >= v[i]; --j) 
       dp[j] = max(dp[i], dp[j - v[i]] + w[i]);
}
// 最大价值为f[m]

体积恰好为m的最大价值

  • 修改初始化策略。dp[0] = 0, dp[i] = INT_MIN, i != 0

体积很大时的01背包

n个物品,每个物品有价值w,体积v,在总体积不超过V,条件下,能装物品的最大价值是多少。

  • 1 <= V <= 1e9
  • 1 <= n <= 100
  • 1 <= w <= 1e3

分析

本题体积范围较大,直接定义体积为 dp[i][j] 为前i个物品,体积不超过j时的最大价值,时间和空间都无法承受。

但是本题的w较小,可以定义 dp[i][j] 为 前i个物品,价值为j时的最小体积。

int maxValue(vector<int> &w, vector<int> &v,int V) {
    vector<long long> dp(100010, 1e18);
    dp[0] = 0;
    for (int i = 0; i < n; ++i) 
        for (int j = 100000; j >= w[i]; --j) 
            dp[j] = min(dp[j], dp[j - w[i]] + v[i]);
    int ans = 0;
    for (int i = 100000; i >= 0; --i)
        if (dp[i] <= V) ans = max(ans, i);
    return ans;
}

完全背包

  • 题目描述:有 N 种物品和一个容量是 V的背包,每种物品都有无限件可用。第 i种物品的体积是 vi,价值是 wi。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。
  • 思路:

f[i] 表示体积为i的情况下,最大价值是多少 res = max(f[0,…,m])

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

完全背包输出方案

  • 输入: 体积数组v,价值数组w,背包容量m。 _ 返回: bool数组cnt(下标0-n-1),cnt[i]表示第i个物品选了多少个。
vector<int> get_ans(vector<int> &v, vector<int> &w, int m) {
    int n = v.size();
    vector<int> f(m + 1), cnt(n);
    vector p(n + 1, vector<bool>(m + 1));
    for (int i = 0; i < n; ++i) {
        for (int j = v[i]; j <= m; ++j) {
            if (f[j] < f[j - v[i]] + w[i]) {
                f[j] = f[j - v[i]] + w[i];
                p[i + 1][j] = true;
            }
        }
    }
    for (int i = n, j = m; i > 0 && j > 0; ) {
        if (p[i][j]) {
            cnt[i - 1] ++;
            j -= v[i - 1];
        }else i--;
    }
    return cnt;
};

多重背包

题目描述:有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

基本算法

这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略微一改即可,因为对于第i种物品有n[i]+1种策略:取0件,取1件……取n[i]件。令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值,则有状态转移方程:

f[i][v]=max{f[i-1][v-kc[i]]+kw[i] 0<=k<=n[i]}

复杂度是O(V*Σn[i])。

转化为01背包问题

另一种好想好写的基本方法是转化为01背包求解:把第i种物品换成n[i]件01背包中的物品,则得到了物品数为Σn[i]的01背包问题,直接求解,复杂度仍然是O(V*Σn[i])。

但是我们期望将它转化为01背包问题之后能够像完全背包一样降低复杂度。仍然考虑二进制的思想,我们考虑把第i种物品换成若干件物品,使得原问题中第i种物品可取的每种策略——取0..n[i]件——均能等价于取若干件代换以后的物品。另外,取超过n[i]件的策略必不能出现。

方法是:将第i种物品分成若干件物品,其中每件物品有一个系数,这件物品的费用和价值均是原来的费用和价值乘以这个系数。使这些系数分别为1,2,4,…,2^(k-1),n[i]-2^k+1,且k是满足n[i]-2^k+1>0的最大整数。例如,如果n[i]为13,就将这种物品分成系数分别为1,2,4,6的四件物品。

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

优化 二进制拆解

struct Good {
  int v, w;
};
vector<Good> goods;
for (int i = 0; i < n; ++i)
{
  for (int k = 1; k <= n[i]; k *= 2) {
    n[i] -= k;
    goods.push_back({v[i] * k, w[i] * k});
  }
  if (n[i] > 0) goods.push_back({v[i] * n[i], w[i] * n[i]});
}

for (auto good :goods) {
  for (int j = m; j >= good.v; --j) {
    f[j] = max(f[j], f[j - good.v] + good.w);
  }
}

混合背包

有 N 种物品和一个容量是 V 的背包。

物品一共有三类:

  • 第一类物品只能用1次(01背包);
  • 第二类物品可以用无限次(完全背包);
  • 第三类物品最多只能用 si 次(多重背包); 每种体积是 vi,价值是 wi。

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

输入格式 第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。

si=−1 表示第 i 种物品只能用1次; si=0 表示第 i 种物品可以用无限次; si>0 表示第 i 种物品可以使用 si 次;

输出格式 输出一个整数,表示最大价值。

#include <bits/stdc++.h>
using namespace std;

const int N = 1010;
int n, m, f[N];

struct Thing {
    int kind, v, w;
};

vector<Thing> things;

int main() {
    cin >> n>>m;
    for(int i = 0;i<n;i++) {
        int v, w, s;
        cin >> v >> w >> s;
        if(s < 0) {
            things.push_back({-1,v,w});
        }else if(s == 0) things.push_back({0,v,w});
        else{
            for(int k = 1;k <= s; k*=2){
                s -=k;
                things.push_back({-1,v*k,w*k});
            }
            if(s > 0) things.push_back({-1,v*s,w*s});
        }
    }

    for(auto thing:things) {
        if(thing.kind < 0){
            for(int j = m;j >= thing.v;j--)   
              f[j] = max(f[j],f[j-thing.v]+thing.w);
        }else{
            for(int j = thing.v;j <= m;j++) 
              f[j] = max(f[j],f[j-thing.v]+thing.w);
        }
    }

    cout << f[m] << endl;
    return 0;
}

二维费用的背包问题

有 N 件物品和一个容量是 V 的背包,背包能承受的最大重量是 M。

每件物品只能用一次。体积是 vi,重量是 mi,价值是 wi。

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

输入格式 第一行两个整数,N,V,M,用空格隔开,分别表示物品件数、背包容积和背包可承受的最大重量。

接下来有 N 行,每行三个整数 vi,mi,wi,用空格隔开,分别表示第 i 件物品的体积、重量和价值。

输出格式 输出一个整数,表示最大价值。

#include<iostream>
using namespace std;
const int N=110;
int n,c,M;
int f[N][N];
int main(){
    cin>>n>>c>>M;
    for(int i=0;i<n;i++){
        int v,m,w;
        cin>>v>>m>>w;
        for(int j=c;j>=v;j--)
          for(int k=M;k>=m;k--)
            f[j][k]=max(f[j][k],f[j-v][k-m]+w);
    }
    cout<<f[c][M]<<endl;
    return 0;
}

分组背包

有 N 组物品和一个容量是 V 的背包。

每组物品有若干个,同一组内的物品最多只能选一个。 每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号。

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

输出最大价值。

输入格式 第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量。

接下来有 N 组数据:

每组数据第一行有一个整数 Si,表示第 i 个物品组的物品数量; 每组数据接下来有 Si 行,每行有两个整数 vij,wij,用空格隔开,分别表示第 i 个物品组的第 j 个物品的体积和价值; 输出格式 输出一个整数,表示最大价值。

#include<iostream>
using namespace std;
const int N=110;
int f[N],s[N];
int v[N][N],w[N][N];
int n,m;
int main(){
   cin>>n>>m;
   for(int i=0;i<n;i++){
     cin>>s[i];
     for(int j=0;j<s[i];j++){
         cin>>v[i][j]>>w[i][j];
     }
   }
   for(int i=0;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]<<endl;
   return 0;
}

有依赖的背包问题

有 N 个物品和一个容量是 V 的背包。

物品之间具有依赖关系,且依赖关系组成一棵树的形状。如果选择一个物品,则必须选择它的父节点。

如下图所示:

     1
    / \
   2   3
  / \
 4   5

如果选择物品5,则必须选择物品1和2。这是因为2是5的父节点,1是2的父节点。

每件物品的编号是 i,体积是 vi,价值是 wi,依赖的父节点编号是 pi。物品的下标范围是 1…N。

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

输出最大价值。

输入格式 第一行有两个整数 N,V,用空格隔开,分别表示物品个数和背包容量。

接下来有 N 行数据,每行数据表示一个物品。 第 i 行有三个整数 vi,wi,pi,用空格隔开,分别表示物品的体积、价值和依赖的物品编号。 如果 pi=−1,表示根节点。 数据保证所有物品构成一棵树。

输出格式 输出一个整数,表示最大价值。

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

const int N = 110;

int n, m;
int h[N], e[N], ne[N], idx;
int v[N], w[N];
int f[N][N];

void add(int x, int y) {
    e[idx] = y, ne[idx] = h[x], h[x] = idx++;
}

void dfs(int x) {
    for (int i = h[x]; i != -1; i = ne[i]) {
        int y = e[i];
        dfs(y);
        for (int j = m - v[x]; j >= 0; j--) {
            for (int k = 0; k <= j; k++) {
                f[x][j] = max(f[x][j], f[x][j - k] + f[y][k]);
            }
        }
    }
    for (int i = m; i >= v[x]; i--) {
        f[x][i] = f[x][i - v[x]] + w[x];
    }
    for (int i = 0; i < v[x]; i++) {
        f[x][i] = 0;
    }
}

int main() {
    memset(h, -1, sizeof h);
    cin >> n >> m;
    int root;
    for (int i = 1; i <= n; i++) {
        int p;
        cin >> v[i] >> w[i] >> p;
        if (p == -1) {
            root = i;
        } else {
            add(p, i);
        }
    }
    dfs(root);
    cout << f[root][m] << endl;
    return 0;
}

打赏一下

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦